[llvm] [VPlan] Add VPIRAttributes; use for VPWidenCallRecipe. (PR #207075)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 16 14:02:45 PDT 2026


https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/207075

>From dff0a23666dc5094c21dea7bcf43cbb0bc2d039b Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Tue, 2 Jun 2026 14:35:51 +0100
Subject: [PATCH 1/2] [VPlan] Add VPIRAttributes; use for VPWidenCallRecipe.

Add a VPIRAttributes mixin (modeled after VPIRMetadata) that stores an
AttributeList for a recipe's underlying call. At construction,
attributes that can safely by used are added (currently non-UB implying
ones like Range, Alignmen, NonNull, FPClass). UB-implying attributes
would be problematic when folding the tail masks off lanes.

We also make sure the attribute is compatible with the widened type.
---
 llvm/lib/Transforms/Vectorize/VPlan.h         |  38 +++++-
 .../lib/Transforms/Vectorize/VPlanRecipes.cpp | 109 +++++++++++++++++-
 .../Transforms/Vectorize/VPlanTransforms.cpp  |   5 +-
 .../AArch64/widen-call-attrs-masked.ll        |   4 +-
 .../LoopVectorize/VPlan/print-attributes.ll   |  72 +++++++++++-
 .../widen-call-attrs-interleave.ll            |   4 +-
 .../LoopVectorize/widen-call-attrs.ll         |  18 +--
 7 files changed, 227 insertions(+), 23 deletions(-)

diff --git a/llvm/lib/Transforms/Vectorize/VPlan.h b/llvm/lib/Transforms/Vectorize/VPlan.h
index eaf9d1433aff7..670ee6fa3dbba 100644
--- a/llvm/lib/Transforms/Vectorize/VPlan.h
+++ b/llvm/lib/Transforms/Vectorize/VPlan.h
@@ -35,6 +35,7 @@
 #include "llvm/Analysis/IVDescriptors.h"
 #include "llvm/Analysis/MemoryLocation.h"
 #include "llvm/Analysis/VectorUtils.h"
+#include "llvm/IR/Attributes.h"
 #include "llvm/IR/DebugLoc.h"
 #include "llvm/IR/FMF.h"
 #include "llvm/IR/Operator.h"
@@ -1216,6 +1217,33 @@ class LLVM_ABI_FOR_TEST VPIRMetadata {
 #endif
 };
 
+/// Manage IR per-argument and return attributes for call recipes. Only
+/// attributes that are valid to propagate to the widened call are stored.
+class VPIRAttributes {
+  AttributeList CallAttrs;
+
+public:
+  VPIRAttributes() = default;
+
+  /// Stores the per-arg/return attributes from \p CI that are valid to
+  /// propagate to a widened call to \p Variant.
+  VPIRAttributes(CallInst &CI, const Function &Variant);
+
+  VPIRAttributes(const VPIRAttributes &Other) = default;
+  VPIRAttributes &operator=(const VPIRAttributes &Other) = default;
+
+  /// Apply the stored per-arg/return attributes to widened call \p V.
+  void applyAttrs(CallInst &V) const;
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+  /// Print return-value attributes carried with this object.
+  void printRetAttrs(raw_ostream &O) const;
+
+  /// Print per-arg attributes for argument \p ArgIdx.
+  void printParamAttrs(raw_ostream &O, unsigned ArgIdx) const;
+#endif
+};
+
 /// This is a concrete Recipe that models a single VPlan-level instruction.
 /// While as any Recipe it may generate a sequence of IR instructions when
 /// executed, these instructions would always form a single-def expression as
@@ -2094,7 +2122,8 @@ class VPWidenMemIntrinsicRecipe final : public VPWidenIntrinsicRecipe {
 
 /// A recipe for widening Call instructions using library calls.
 class LLVM_ABI_FOR_TEST VPWidenCallRecipe : public VPRecipeWithIRFlags,
-                                            public VPIRMetadata {
+                                            public VPIRMetadata,
+                                            public VPIRAttributes {
   /// Variant stores a pointer to the chosen function. There is a 1:1 mapping
   /// between a given VF and the chosen vectorized variant, so there will be a
   /// different VPlan for each VF with a valid variant.
@@ -2104,11 +2133,12 @@ class LLVM_ABI_FOR_TEST VPWidenCallRecipe : public VPRecipeWithIRFlags,
   VPWidenCallRecipe(Value *UV, Function *Variant,
                     ArrayRef<VPValue *> CallArguments,
                     const VPIRFlags &Flags = {},
-                    const VPIRMetadata &Metadata = {}, DebugLoc DL = {})
+                    const VPIRMetadata &Metadata = {},
+                    const VPIRAttributes &Attrs = {}, DebugLoc DL = {})
       : VPRecipeWithIRFlags(VPRecipeBase::VPWidenCallSC, CallArguments,
                             toScalarizedTy(Variant->getReturnType()), Flags,
                             DL),
-        VPIRMetadata(Metadata), Variant(Variant) {
+        VPIRMetadata(Metadata), VPIRAttributes(Attrs), Variant(Variant) {
     setUnderlyingValue(UV);
     assert(
         isa<Function>(getOperand(getNumOperands() - 1)->getLiveInIRValue()) &&
@@ -2122,7 +2152,7 @@ class LLVM_ABI_FOR_TEST VPWidenCallRecipe : public VPRecipeWithIRFlags,
 
   VPWidenCallRecipe *clone() override {
     return new VPWidenCallRecipe(getUnderlyingValue(), Variant, operands(),
-                                 *this, *this, getDebugLoc());
+                                 *this, *this, *this, getDebugLoc());
   }
 
   VP_CLASSOF_IMPL(VPRecipeBase::VPWidenCallSC)
diff --git a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
index f45b9e4f6c35b..fa48ae31e8b1b 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
@@ -24,6 +24,8 @@
 #include "llvm/Analysis/IVDescriptors.h"
 #include "llvm/Analysis/LoopInfo.h"
 #include "llvm/Analysis/ScalarEvolutionExpressions.h"
+#include "llvm/IR/AttributeMask.h"
+#include "llvm/IR/Attributes.h"
 #include "llvm/IR/BasicBlock.h"
 #include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/Instruction.h"
@@ -2121,6 +2123,101 @@ void VPIRMetadata::print(raw_ostream &O, VPSlotTracker &SlotTracker) const {
 }
 #endif
 
+VPIRAttributes::VPIRAttributes(CallInst &CI, const Function &Variant) {
+  LLVMContext &Ctx = CI.getContext();
+  AttributeList ScalarAttrs = CI.getAttributes();
+  AttributeList VariantAttrs = Variant.getAttributes();
+  FunctionType *VariantTy = Variant.getFunctionType();
+
+  static constexpr Attribute::AttrKind Allowed[] = {
+      Attribute::Alignment, Attribute::NonNull, Attribute::NoFPClass,
+      Attribute::Range};
+
+  // Combine attributes from the Variant declaration and the call site, picking
+  // the stricter one.
+  auto CombineAttrs = [&](Attribute CallAttr,
+                          Attribute VariantAttr) -> Attribute {
+    if (!VariantAttr.isValid())
+      return CallAttr;
+    switch (CallAttr.getKindAsEnum()) {
+    case Attribute::Alignment:
+      return Attribute::getWithAlignment(
+          Ctx, std::max(CallAttr.getAlignment().valueOrOne(),
+                        VariantAttr.getAlignment().valueOrOne()));
+    case Attribute::NoFPClass:
+      return Attribute::getWithNoFPClass(Ctx, CallAttr.getNoFPClass() |
+                                                  VariantAttr.getNoFPClass());
+    case Attribute::Range:
+      return Attribute::get(
+          Ctx, Attribute::Range,
+          CallAttr.getRange().intersectWith(VariantAttr.getRange()));
+    default:
+      // Boolean attributes (e.g. nonnull) carry no value to combine.
+      return CallAttr;
+    }
+  };
+
+  // Only keep allowed attributes (which do not trigger UB) and ones that are
+  // compatible with the widened type of Variant.
+  auto Filter = [&](Type *Ty, AttributeSet CallAS, AttributeSet VariantAS) {
+    AttributeMask Incompatible =
+        AttributeFuncs::typeIncompatible(Ty, CallAS, AttributeFuncs::ASK_ALL);
+    AttrBuilder AB(Ctx);
+    for (Attribute::AttrKind K : Allowed) {
+      if (CallAS.hasAttribute(K) && !Incompatible.contains(K))
+        AB.addAttribute(
+            CombineAttrs(CallAS.getAttribute(K), VariantAS.getAttribute(K)));
+    }
+    return AttributeSet::get(Ctx, AB);
+  };
+
+  AttributeSet RetAttrs;
+  if (!Variant.getReturnType()->isVoidTy())
+    RetAttrs = Filter(Variant.getReturnType(), ScalarAttrs.getRetAttrs(),
+                      VariantAttrs.getRetAttrs());
+
+  SmallVector<AttributeSet> ArgAttrs(VariantTy->getNumParams());
+  for (unsigned I = 0, E = std::min<unsigned>(CI.arg_size(),
+                                              VariantTy->getNumParams());
+       I != E; ++I) {
+    AttributeSet PA = ScalarAttrs.getParamAttrs(I);
+    if (!PA.hasAttributes())
+      continue;
+    ArgAttrs[I] =
+        Filter(VariantTy->getParamType(I), PA, VariantAttrs.getParamAttrs(I));
+  }
+
+  CallAttrs = AttributeList::get(Ctx, AttributeSet(), RetAttrs, ArgAttrs);
+}
+
+void VPIRAttributes::applyAttrs(CallInst &V) const {
+  LLVMContext &Ctx = V.getContext();
+
+  AttributeSet RetAttrs = CallAttrs.getRetAttrs();
+  if (RetAttrs.hasAttributes())
+    V.addRetAttrs(AttrBuilder(Ctx, RetAttrs));
+
+  for (unsigned I = 0, E = V.arg_size(); I != E; ++I) {
+    AttributeSet PA = CallAttrs.getParamAttrs(I);
+    if (PA.hasAttributes())
+      V.addParamAttrs(I, AttrBuilder(Ctx, PA));
+  }
+}
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+void VPIRAttributes::printRetAttrs(raw_ostream &O) const {
+  AttributeSet RetAttrs = CallAttrs.getRetAttrs();
+  if (RetAttrs.hasAttributes())
+    O << " " << RetAttrs.getAsString();
+}
+
+void VPIRAttributes::printParamAttrs(raw_ostream &O, unsigned ArgIdx) const {
+  AttributeSet PA = CallAttrs.getParamAttrs(ArgIdx);
+  if (PA.hasAttributes())
+    O << PA.getAsString() << " ";
+}
+#endif
+
 void VPWidenCallRecipe::execute(VPTransformState &State) {
   assert(State.VF.isVector() && "not widening");
   assert(Variant != nullptr && "Can't create vector function.");
@@ -2150,6 +2247,8 @@ void VPWidenCallRecipe::execute(VPTransformState &State) {
   applyMetadata(*V);
   V->setCallingConv(Variant->getCallingConv());
 
+  applyAttrs(*V);
+
   if (!V->getType()->isVoidTy())
     State.set(this, V);
 }
@@ -2195,10 +2294,14 @@ void VPWidenCallRecipe::printRecipe(raw_ostream &O, const Twine &Indent,
 
   O << "call";
   printFlags(O);
+  printRetAttrs(O);
   O << " @" << CalledFn->getName() << "(";
-  interleaveComma(args(), O, [&O, &SlotTracker](VPValue *Op) {
-    Op->printAsOperand(O, SlotTracker);
-  });
+  interleaveComma(enumerate(args()), O,
+                  [&O, &SlotTracker, this](const auto &IndexedOp) {
+                    auto [Idx, Op] = IndexedOp;
+                    printParamAttrs(O, Idx);
+                    Op->printAsOperand(O, SlotTracker);
+                  });
   O << ")";
 
   O << " (using library function";
diff --git a/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp b/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
index ceb7c38ca3e29..bf238735bb7d8 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
@@ -7497,8 +7497,9 @@ void VPlanTransforms::makeCallWideningDecisions(VPlan &Plan, VFRange &Range,
           Ops.push_back(Mask);
         }
         Ops.push_back(VPI->getOperand(VPI->getNumOperandsWithoutMask() - 1));
-        Replacement = new VPWidenCallRecipe(CI, Decision.Variant, Ops, *VPI,
-                                            *VPI, VPI->getDebugLoc());
+        Replacement = new VPWidenCallRecipe(
+            CI, Decision.Variant, Ops, *VPI, *VPI,
+            VPIRAttributes(*CI, *Decision.Variant), VPI->getDebugLoc());
         break;
       }
       case CallWideningDecision::KindTy::Scalarize:
diff --git a/llvm/test/Transforms/LoopVectorize/AArch64/widen-call-attrs-masked.ll b/llvm/test/Transforms/LoopVectorize/AArch64/widen-call-attrs-masked.ll
index 24d85bfdea0b5..d7e04494fd4ee 100644
--- a/llvm/test/Transforms/LoopVectorize/AArch64/widen-call-attrs-masked.ll
+++ b/llvm/test/Transforms/LoopVectorize/AArch64/widen-call-attrs-masked.ll
@@ -25,7 +25,7 @@ define void @masked_range_arg_and_ret_propagated(ptr noalias %a, ptr readnone %b
 ; CHECK-NEXT:    [[ACTIVE_LANE_MASK:%.*]] = phi <vscale x 2 x i1> [ [[ACTIVE_LANE_MASK_ENTRY]], %[[VECTOR_PH]] ], [ [[ACTIVE_LANE_MASK_NEXT:%.*]], %[[VECTOR_BODY]] ]
 ; CHECK-NEXT:    [[TMP2:%.*]] = getelementptr i64, ptr [[B]], i64 [[INDEX]]
 ; CHECK-NEXT:    [[WIDE_MASKED_LOAD:%.*]] = call <vscale x 2 x i64> @llvm.masked.load.nxv2i64.p0(ptr align 8 [[TMP2]], <vscale x 2 x i1> [[ACTIVE_LANE_MASK]], <vscale x 2 x i64> poison)
-; CHECK-NEXT:    [[TMP3:%.*]] = call <vscale x 2 x i64> @foo_vector(<vscale x 2 x i64> [[WIDE_MASKED_LOAD]], <vscale x 2 x i1> [[ACTIVE_LANE_MASK]])
+; CHECK-NEXT:    [[TMP3:%.*]] = call range(i64 0, 100) <vscale x 2 x i64> @foo_vector(<vscale x 2 x i64> range(i64 0, 50) [[WIDE_MASKED_LOAD]], <vscale x 2 x i1> [[ACTIVE_LANE_MASK]])
 ; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr i64, ptr [[A]], i64 [[INDEX]]
 ; CHECK-NEXT:    call void @llvm.masked.store.nxv2i64.p0(<vscale x 2 x i64> [[TMP3]], ptr align 8 [[TMP4]], <vscale x 2 x i1> [[ACTIVE_LANE_MASK]])
 ; CHECK-NEXT:    [[INDEX_NEXT]] = add i64 [[INDEX]], [[TMP1]]
@@ -72,7 +72,7 @@ define void @masked_noundef_dereferenceable_stripped(ptr noalias %a, ptr readnon
 ; CHECK-NEXT:    [[ACTIVE_LANE_MASK:%.*]] = phi <vscale x 2 x i1> [ [[ACTIVE_LANE_MASK_ENTRY]], %[[VECTOR_PH]] ], [ [[ACTIVE_LANE_MASK_NEXT:%.*]], %[[VECTOR_BODY]] ]
 ; CHECK-NEXT:    [[TMP2:%.*]] = getelementptr i64, ptr [[B]], i64 [[INDEX]]
 ; CHECK-NEXT:    [[WIDE_MASKED_LOAD:%.*]] = call <vscale x 2 x i64> @llvm.masked.load.nxv2i64.p0(ptr align 8 [[TMP2]], <vscale x 2 x i1> [[ACTIVE_LANE_MASK]], <vscale x 2 x i64> poison)
-; CHECK-NEXT:    [[TMP3:%.*]] = call <vscale x 2 x i64> @foo_vector(<vscale x 2 x i64> [[WIDE_MASKED_LOAD]], <vscale x 2 x i1> [[ACTIVE_LANE_MASK]])
+; CHECK-NEXT:    [[TMP3:%.*]] = call range(i64 0, 100) <vscale x 2 x i64> @foo_vector(<vscale x 2 x i64> range(i64 0, 50) [[WIDE_MASKED_LOAD]], <vscale x 2 x i1> [[ACTIVE_LANE_MASK]])
 ; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr i64, ptr [[A]], i64 [[INDEX]]
 ; CHECK-NEXT:    call void @llvm.masked.store.nxv2i64.p0(<vscale x 2 x i64> [[TMP3]], ptr align 8 [[TMP4]], <vscale x 2 x i1> [[ACTIVE_LANE_MASK]])
 ; CHECK-NEXT:    [[INDEX_NEXT]] = add i64 [[INDEX]], [[TMP1]]
diff --git a/llvm/test/Transforms/LoopVectorize/VPlan/print-attributes.ll b/llvm/test/Transforms/LoopVectorize/VPlan/print-attributes.ll
index d7f46b8a62a5a..791cc20a7447e 100644
--- a/llvm/test/Transforms/LoopVectorize/VPlan/print-attributes.ll
+++ b/llvm/test/Transforms/LoopVectorize/VPlan/print-attributes.ll
@@ -6,6 +6,9 @@ target triple = "arm64-apple-macosx"
 declare double @acos(double)
 declare <2 x double> @vec_acos(<2 x double>)
 
+declare i64 @bar(i64)
+declare range(i64 0, 60) <2 x i64> @vec_bar(<2 x i64> range(i64 0, 50))
+
 define void @wide_call_attrs(ptr noalias %in.ptr, ptr noalias %out.ptr) {
 ; CHECK-LABEL: VPlan for loop in 'wide_call_attrs'
 ; CHECK:  VPlan ' for UF>=1' {
@@ -28,7 +31,7 @@ define void @wide_call_attrs(ptr noalias %in.ptr, ptr noalias %out.ptr) {
 ; CHECK-NEXT:      CLONE ir<%in.gep> = getelementptr inbounds ir<%in.ptr>, ir<%iv>
 ; CHECK-NEXT:      vp<[[VP4:%[0-9]+]]> = vector-pointer inbounds ir<%in.gep>, ir<1>
 ; CHECK-NEXT:      WIDEN ir<%in> = load vp<[[VP4]]>
-; CHECK-NEXT:      WIDEN-CALL ir<%call> = call  @acos(ir<%in>) (using library function: vec_acos)
+; CHECK-NEXT:      WIDEN-CALL ir<%call> = call  nofpclass(nan) @acos(nofpclass(inf) ir<%in>) (using library function: vec_acos)
 ; CHECK-NEXT:      CLONE ir<%out.gep> = getelementptr inbounds ir<%out.ptr>, ir<%iv>
 ; CHECK-NEXT:      vp<[[VP5:%[0-9]+]]> = vector-pointer inbounds ir<%out.gep>, ir<1>
 ; CHECK-NEXT:      WIDEN store vp<[[VP5]]>, ir<%call>
@@ -69,6 +72,72 @@ exit:
   ret void
 }
 
+; The call site carries a propagatable (range) and a non-propagatable
+; (noundef) attribute on both the argument and return. Make sure only the valid
+; attributes are printed.
+define void @wide_call_attrs_filtered_and_combined(ptr noalias %in.ptr, ptr noalias %out.ptr) {
+; CHECK-LABEL: VPlan for loop in 'wide_call_attrs_filtered_and_combined'
+; CHECK:  VPlan ' for UF>=1' {
+; CHECK-NEXT:  Live-in vp<[[VP0:%[0-9]+]]> = VF
+; CHECK-NEXT:  Live-in vp<[[VP1:%[0-9]+]]> = VF * UF
+; CHECK-NEXT:  Live-in vp<[[VP2:%[0-9]+]]> = vector-trip-count
+; CHECK-NEXT:  Live-in ir<1000> = original trip-count
+; CHECK-EMPTY:
+; CHECK-NEXT:  ir-bb<entry>:
+; CHECK-NEXT:  Successor(s): scalar.ph, vector.ph
+; CHECK-EMPTY:
+; CHECK-NEXT:  vector.ph:
+; CHECK-NEXT:  Successor(s): vector loop
+; CHECK-EMPTY:
+; CHECK-NEXT:  <x1> vector loop: {
+; CHECK-NEXT:  vp<[[VP3:%[0-9]+]]> = CANONICAL-IV
+; CHECK-EMPTY:
+; CHECK-NEXT:    vector.body:
+; CHECK-NEXT:      ir<%iv> = WIDEN-INDUCTION nuw nsw ir<0>, ir<1>, vp<[[VP0]]>
+; CHECK-NEXT:      CLONE ir<%in.gep> = getelementptr inbounds ir<%in.ptr>, ir<%iv>
+; CHECK-NEXT:      vp<[[VP4:%[0-9]+]]> = vector-pointer inbounds ir<%in.gep>, ir<1>
+; CHECK-NEXT:      WIDEN ir<%in> = load vp<[[VP4]]>
+; CHECK-NEXT:      WIDEN-CALL ir<%call> = call  range(i64 0, 60) @bar(range(i64 0, 50) ir<%in>) (using library function: vec_bar)
+; CHECK-NEXT:      CLONE ir<%out.gep> = getelementptr inbounds ir<%out.ptr>, ir<%iv>
+; CHECK-NEXT:      vp<[[VP5:%[0-9]+]]> = vector-pointer inbounds ir<%out.gep>, ir<1>
+; CHECK-NEXT:      WIDEN store vp<[[VP5]]>, ir<%call>
+; CHECK-NEXT:      EMIT ir<%iv.next> = add nuw nsw ir<%iv>, ir<1>
+; CHECK-NEXT:      CLONE ir<%exitcond> = icmp eq ir<%iv.next>, ir<1000>
+; CHECK-NEXT:      EMIT vp<%index.next> = add nuw vp<[[VP3]]>, vp<[[VP1]]>
+; CHECK-NEXT:      EMIT branch-on-count vp<%index.next>, vp<[[VP2]]>
+; CHECK-NEXT:    No successors
+; CHECK-NEXT:  }
+; CHECK-NEXT:  Successor(s): middle.block
+; CHECK-EMPTY:
+; CHECK-NEXT:  middle.block:
+; CHECK-NEXT:    EMIT vp<[[VP7:%[0-9]+]]> = exiting-iv-value ir<%iv>
+; CHECK-NEXT:    EMIT vp<%cmp.n> = icmp eq ir<1000>, vp<[[VP2]]>
+; CHECK-NEXT:    EMIT branch-on-cond vp<%cmp.n>
+; CHECK-NEXT:  Successor(s): ir-bb<exit>, scalar.ph
+; CHECK-EMPTY:
+; CHECK-NEXT:  ir-bb<exit>:
+; CHECK-NEXT:  No successors
+; CHECK-EMPTY:
+; CHECK-NEXT:  scalar.ph:
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+  %in.gep = getelementptr inbounds i64, ptr %in.ptr, i64 %iv
+  %in = load i64, ptr %in.gep, align 8
+  %call = tail call range(i64 0, 100) noundef i64 @bar(i64 range(i64 0, 80) noundef %in) #1
+  %out.gep = getelementptr inbounds i64, ptr %out.ptr, i64 %iv
+  store i64 %call, ptr %out.gep, align 8
+  %iv.next = add nuw nsw i64 %iv, 1
+  %exitcond = icmp eq i64 %iv.next, 1000
+  br i1 %exitcond, label %exit, label %loop
+
+exit:
+  ret void
+}
+
 define void @test_intrinsic_with_arg_and_ret_attrs(ptr noalias %A, ptr noalias %B, i32 %n) {
 ; CHECK-LABEL: VPlan for loop in 'test_intrinsic_with_arg_and_ret_attrs'
 ; CHECK:  VPlan ' for UF>=1' {
@@ -136,3 +205,4 @@ exit:
 }
 
 attributes #0 = { "vector-function-abi-variant"="_ZGVnN2v_acos(vec_acos)" }
+attributes #1 = { "vector-function-abi-variant"="_ZGVnN2v_bar(vec_bar)" }
diff --git a/llvm/test/Transforms/LoopVectorize/widen-call-attrs-interleave.ll b/llvm/test/Transforms/LoopVectorize/widen-call-attrs-interleave.ll
index b68f1545aad6a..2d19e2ae74b19 100644
--- a/llvm/test/Transforms/LoopVectorize/widen-call-attrs-interleave.ll
+++ b/llvm/test/Transforms/LoopVectorize/widen-call-attrs-interleave.ll
@@ -17,8 +17,8 @@ define void @range_propagated_uf2(ptr noalias %src, ptr noalias %out) {
 ; CHECK-NEXT:    [[TMP1:%.*]] = getelementptr i32, ptr [[TMP0]], i64 2
 ; CHECK-NEXT:    [[WIDE_LOAD:%.*]] = load <2 x i32>, ptr [[TMP0]], align 4
 ; CHECK-NEXT:    [[WIDE_LOAD1:%.*]] = load <2 x i32>, ptr [[TMP1]], align 4
-; CHECK-NEXT:    [[TMP2:%.*]] = call <2 x i32> @vec_fn_int(<2 x i32> [[WIDE_LOAD]])
-; CHECK-NEXT:    [[TMP3:%.*]] = call <2 x i32> @vec_fn_int(<2 x i32> [[WIDE_LOAD1]])
+; CHECK-NEXT:    [[TMP2:%.*]] = call <2 x i32> @vec_fn_int(<2 x i32> range(i32 0, 100) [[WIDE_LOAD]])
+; CHECK-NEXT:    [[TMP3:%.*]] = call <2 x i32> @vec_fn_int(<2 x i32> range(i32 0, 100) [[WIDE_LOAD1]])
 ; CHECK-NEXT:    [[TMP4:%.*]] = getelementptr i32, ptr [[OUT]], i64 [[INDEX]]
 ; CHECK-NEXT:    [[TMP5:%.*]] = getelementptr i32, ptr [[TMP4]], i64 2
 ; CHECK-NEXT:    store <2 x i32> [[TMP2]], ptr [[TMP4]], align 4
diff --git a/llvm/test/Transforms/LoopVectorize/widen-call-attrs.ll b/llvm/test/Transforms/LoopVectorize/widen-call-attrs.ll
index 071eb9b001027..0ec410b87bdd0 100644
--- a/llvm/test/Transforms/LoopVectorize/widen-call-attrs.ll
+++ b/llvm/test/Transforms/LoopVectorize/widen-call-attrs.ll
@@ -157,7 +157,7 @@ define void @range_arg_propagated(ptr noalias %src, ptr noalias %out) {
 ; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
 ; CHECK-NEXT:    [[TMP0:%.*]] = getelementptr i32, ptr [[SRC]], i64 [[INDEX]]
 ; CHECK-NEXT:    [[WIDE_LOAD:%.*]] = load <2 x i32>, ptr [[TMP0]], align 4
-; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x i32> @vec_fn_int(<2 x i32> [[WIDE_LOAD]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x i32> @vec_fn_int(<2 x i32> range(i32 0, 100) [[WIDE_LOAD]])
 ; CHECK-NEXT:    [[TMP2:%.*]] = getelementptr i32, ptr [[OUT]], i64 [[INDEX]]
 ; CHECK-NEXT:    store <2 x i32> [[TMP1]], ptr [[TMP2]], align 4
 ; CHECK-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 2
@@ -198,7 +198,7 @@ define void @nofpclass_propagated(ptr noalias %src, ptr noalias %out) {
 ; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
 ; CHECK-NEXT:    [[TMP0:%.*]] = getelementptr float, ptr [[SRC]], i64 [[INDEX]]
 ; CHECK-NEXT:    [[WIDE_LOAD:%.*]] = load <2 x float>, ptr [[TMP0]], align 4
-; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x float> @vec_fn_fp(<2 x float> [[WIDE_LOAD]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call nofpclass(nan) <2 x float> @vec_fn_fp(<2 x float> nofpclass(nan) [[WIDE_LOAD]])
 ; CHECK-NEXT:    [[TMP2:%.*]] = getelementptr float, ptr [[OUT]], i64 [[INDEX]]
 ; CHECK-NEXT:    store <2 x float> [[TMP1]], ptr [[TMP2]], align 4
 ; CHECK-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 2
@@ -234,7 +234,7 @@ define void @align_uniform_ptr_propagated(ptr noalias %p, ptr noalias %out) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    br label %[[VECTOR_PH:.*]]
 ; CHECK:       [[VECTOR_PH]]:
-; CHECK-NEXT:    [[TMP0:%.*]] = call <2 x i32> @vec_fn_uniform_ptr(ptr [[P]])
+; CHECK-NEXT:    [[TMP0:%.*]] = call <2 x i32> @vec_fn_uniform_ptr(ptr align 16 [[P]])
 ; CHECK-NEXT:    br label %[[VECTOR_BODY:.*]]
 ; CHECK:       [[VECTOR_BODY]]:
 ; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
@@ -271,7 +271,7 @@ define void @nonnull_uniform_ptr_propagated(ptr noalias %p, ptr noalias %out) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    br label %[[VECTOR_PH:.*]]
 ; CHECK:       [[VECTOR_PH]]:
-; CHECK-NEXT:    [[TMP0:%.*]] = call <2 x i32> @vec_fn_uniform_ptr(ptr [[P]])
+; CHECK-NEXT:    [[TMP0:%.*]] = call <2 x i32> @vec_fn_uniform_ptr(ptr nonnull [[P]])
 ; CHECK-NEXT:    br label %[[VECTOR_BODY:.*]]
 ; CHECK:       [[VECTOR_BODY]]:
 ; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
@@ -353,7 +353,7 @@ define void @range_arg_variant_decl_wins(ptr noalias %src, ptr noalias %out) {
 ; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
 ; CHECK-NEXT:    [[TMP0:%.*]] = getelementptr i32, ptr [[SRC]], i64 [[INDEX]]
 ; CHECK-NEXT:    [[WIDE_LOAD:%.*]] = load <2 x i32>, ptr [[TMP0]], align 4
-; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x i32> @vec_fn_int_variant_attrs(<2 x i32> [[WIDE_LOAD]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x i32> @vec_fn_int_variant_attrs(<2 x i32> range(i32 0, 50) [[WIDE_LOAD]])
 ; CHECK-NEXT:    [[TMP2:%.*]] = getelementptr i32, ptr [[OUT]], i64 [[INDEX]]
 ; CHECK-NEXT:    store <2 x i32> [[TMP1]], ptr [[TMP2]], align 4
 ; CHECK-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 2
@@ -391,7 +391,7 @@ define void @align_uniform_ptr_variant_decl_wins(ptr noalias %p, ptr noalias %ou
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    br label %[[VECTOR_PH:.*]]
 ; CHECK:       [[VECTOR_PH]]:
-; CHECK-NEXT:    [[TMP0:%.*]] = call <2 x i32> @vec_fn_uniform_ptr_variant_attrs(ptr [[P]])
+; CHECK-NEXT:    [[TMP0:%.*]] = call <2 x i32> @vec_fn_uniform_ptr_variant_attrs(ptr align 32 [[P]])
 ; CHECK-NEXT:    br label %[[VECTOR_BODY:.*]]
 ; CHECK:       [[VECTOR_BODY]]:
 ; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
@@ -434,7 +434,7 @@ define void @nofpclass_arg_variant_decl_wins(ptr noalias %src, ptr noalias %out)
 ; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
 ; CHECK-NEXT:    [[TMP0:%.*]] = getelementptr float, ptr [[SRC]], i64 [[INDEX]]
 ; CHECK-NEXT:    [[WIDE_LOAD:%.*]] = load <2 x float>, ptr [[TMP0]], align 4
-; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x float> @vec_fn_fp_variant_attrs(<2 x float> [[WIDE_LOAD]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x float> @vec_fn_fp_variant_attrs(<2 x float> nofpclass(nan inf) [[WIDE_LOAD]])
 ; CHECK-NEXT:    [[TMP2:%.*]] = getelementptr float, ptr [[OUT]], i64 [[INDEX]]
 ; CHECK-NEXT:    store <2 x float> [[TMP1]], ptr [[TMP2]], align 4
 ; CHECK-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 2
@@ -475,7 +475,7 @@ define void @range_ret_propagated(ptr noalias %src, ptr noalias %out) {
 ; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
 ; CHECK-NEXT:    [[TMP0:%.*]] = getelementptr i32, ptr [[SRC]], i64 [[INDEX]]
 ; CHECK-NEXT:    [[WIDE_LOAD:%.*]] = load <2 x i32>, ptr [[TMP0]], align 4
-; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x i32> @vec_fn_int(<2 x i32> [[WIDE_LOAD]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call range(i32 0, 100) <2 x i32> @vec_fn_int(<2 x i32> [[WIDE_LOAD]])
 ; CHECK-NEXT:    [[TMP2:%.*]] = getelementptr i32, ptr [[OUT]], i64 [[INDEX]]
 ; CHECK-NEXT:    store <2 x i32> [[TMP1]], ptr [[TMP2]], align 4
 ; CHECK-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 2
@@ -516,7 +516,7 @@ define void @align_vec_ptr_propagated(ptr noalias %dst, ptr noalias %out) {
 ; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
 ; CHECK-NEXT:    [[TMP0:%.*]] = getelementptr ptr, ptr [[DST]], i64 [[INDEX]]
 ; CHECK-NEXT:    [[WIDE_LOAD:%.*]] = load <2 x ptr>, ptr [[TMP0]], align 8
-; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x i32> @vec_fn_vec_ptr(<2 x ptr> [[WIDE_LOAD]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x i32> @vec_fn_vec_ptr(<2 x ptr> align 16 [[WIDE_LOAD]])
 ; CHECK-NEXT:    [[TMP2:%.*]] = getelementptr i32, ptr [[OUT]], i64 [[INDEX]]
 ; CHECK-NEXT:    store <2 x i32> [[TMP1]], ptr [[TMP2]], align 4
 ; CHECK-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 2

>From 15b948cd2f43a754365a6659143137c2536bf460 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Thu, 16 Jul 2026 22:02:07 +0100
Subject: [PATCH 2/2] !fixup add TODO to support more attributes

---
 llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
index b703275d0367f..08b1f1088a63b 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
@@ -2148,6 +2148,7 @@ VPIRAttributes::VPIRAttributes(CallInst &CI, const Function &Variant) {
   AttributeList VariantAttrs = Variant.getAttributes();
   FunctionType *VariantTy = Variant.getFunctionType();
 
+  // TODO: Support additional attributes.
   static constexpr Attribute::AttrKind Allowed[] = {
       Attribute::Alignment, Attribute::NonNull, Attribute::NoFPClass,
       Attribute::Range};



More information about the llvm-commits mailing list