[llvm] [Verifier] Add checks for loop metadata nodes (PR #182252)

via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 19 03:50:25 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-ir

Author: Madhur Amilkanthwar (madhur13490)

<details>
<summary>Changes</summary>

This patch adds checks in verifier to error if loop metadata enable and disable nodes are not following the synatx as per LangRef.

Particualrly, the below metadata must have name and i1.
    "llvm.loop.distribute.enable"
    "llvm.loop.vectorize.enable"

e.g.
!0 = !{!"llvm.loop.distribute.enable", i1 0}

and the below ones should have just the name.
    "llvm.loop.unroll.disable"
    "llvm.loop.unroll.enable"
    "llvm.loop.unroll.full"
    "llvm.loop.unroll_and_jam.enable"
    "llvm.loop.unroll_and_jam.disable"

e.g.
    !0 = !{!"llvm.loop.unroll.disable"}

This patch also fixes inconsistencies in the exisiting tests and adds malformed IR tests which should not go beyond verifier.

Fixes #<!-- -->156685

---
Full diff: https://github.com/llvm/llvm-project/pull/182252.diff


10 Files Affected:

- (modified) llvm/lib/IR/Verifier.cpp (+57) 
- (modified) llvm/test/Transforms/IndVarSimplify/pr68260.ll (+1-1) 
- (modified) llvm/test/Transforms/LoopDistribute/disable_nonforced_enable.ll (+1-1) 
- (added) llvm/test/Transforms/LoopDistribute/malformed-metadata.ll (+34) 
- (modified) llvm/test/Transforms/LoopTransformWarning/distribution-remarks-missed.ll (+1-1) 
- (modified) llvm/test/Transforms/LoopTransformWarning/optnone.ll (+2-2) 
- (added) llvm/test/Transforms/LoopUnroll/malformed-metadata.ll (+17) 
- (added) llvm/test/Transforms/LoopUnrollAndJam/malformed-metadata.ll (+17) 
- (modified) llvm/test/Transforms/LoopVectorize/disable_nonforced_enable.ll (+1-1) 
- (modified) llvm/test/Transforms/LoopVectorize/remove_metadata.ll (+1-1) 


``````````diff
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index f986f5406b2b3..31a4c175503d8 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -546,6 +546,7 @@ class Verifier : public InstVisitor<Verifier>, VerifierSupport {
   void visitAccessGroupMetadata(const MDNode *MD);
   void visitCapturesMetadata(Instruction &I, const MDNode *Captures);
   void visitAllocTokenMetadata(Instruction &I, MDNode *MD);
+  void verifyLoopMetadata(const MDNode *LoopID);
 
   template <class Ty> bool isValidMetadataArray(const MDTuple &N);
 #define HANDLE_SPECIALIZED_MDNODE_LEAF(CLASS) void visit##CLASS(const CLASS &N);
@@ -1145,6 +1146,60 @@ void Verifier::visitMDNode(const MDNode &MD, AreDebugLocsAllowed AllowLocs) {
   Check(MD.isResolved(), "All nodes should be resolved!", &MD);
 }
 
+// Loop attributes that require exactly two operands (name + i1).
+static const char *const LoopBooleanAttributeNames[] = {
+    "llvm.loop.distribute.enable",
+    "llvm.loop.vectorize.enable",
+};
+
+// Loop attributes that allow only a single operand (just the name).
+// TODO: This is not an exhaustive list and can also include attributes
+//       like llvm.loop.licm_versioning.disable.
+static const char *const LoopSingleOperandAttributeNames[] = {
+    "llvm.loop.unroll.disable",
+    "llvm.loop.unroll.enable",
+    "llvm.loop.unroll.full",
+    "llvm.loop.unroll_and_jam.enable",
+    "llvm.loop.unroll_and_jam.disable",
+};
+
+void Verifier::verifyLoopMetadata(const MDNode *LoopID) {
+  // Allow empty loop metadata (!{}) so the verifier does not crash on malformed
+  // IR; skip further checks.
+  if (LoopID->getNumOperands() == 0)
+    return;
+  Check(LoopID->getNumOperands() >= 1,
+        "llvm.loop metadata must have at least one operand", LoopID);
+  for (const MDOperand &Op : llvm::drop_begin(LoopID->operands())) {
+    MDNode *Option = dyn_cast<MDNode>(Op);
+    if (!Option || Option->getNumOperands() < 1)
+      continue;
+    MDString *Name = dyn_cast<MDString>(Option->getOperand(0));
+    if (!Name)
+      continue;
+    StringRef AttrName = Name->getString();
+
+    // Verify single-operand attributes.
+    if (llvm::is_contained(LoopSingleOperandAttributeNames, AttrName)) {
+      Check(Option->getNumOperands() == 1,
+            "llvm.loop attribute must have exactly one operand", Option);
+      continue;
+    }
+
+    // Verify boolean attributes.
+    if (llvm::is_contained(LoopBooleanAttributeNames, AttrName)) {
+      // Boolean loop attribute: exactly 2 operands (name + i1).
+      Check(Option->getNumOperands() == 2,
+            "llvm.loop boolean attribute must have exactly two operands", Option);
+      auto *Val =
+          mdconst::dyn_extract_or_null<ConstantInt>(Option->getOperand(1));
+      Check(Val && Val->getType()->isIntegerTy(1),
+            "llvm.loop boolean attribute second operand must be i1", Option);
+      continue;
+    }
+  }
+}
+
 void Verifier::visitValueAsMetadata(const ValueAsMetadata &MD, Function *F) {
   Check(MD.getValue(), "Expected valid value", &MD);
   Check(!MD.getValue()->getType()->isMetadataTy(),
@@ -5831,6 +5886,8 @@ void Verifier::visitInstruction(Instruction &I) {
             ? AreDebugLocsAllowed::Yes
             : AreDebugLocsAllowed::No;
     visitMDNode(*Attachment.second, AllowLocs);
+    if (Kind == LLVMContext::MD_loop)
+      verifyLoopMetadata(Attachment.second);
   }
 
   InstsInThisBlock.insert(&I);
diff --git a/llvm/test/Transforms/IndVarSimplify/pr68260.ll b/llvm/test/Transforms/IndVarSimplify/pr68260.ll
index fb185d2015b64..df5f2ee070bd2 100644
--- a/llvm/test/Transforms/IndVarSimplify/pr68260.ll
+++ b/llvm/test/Transforms/IndVarSimplify/pr68260.ll
@@ -49,4 +49,4 @@ exit:
 }
 
 !0 = distinct !{!0, !1}
-!1 = !{!"llvm.loop.unroll.disable", i1 true}
+!1 = !{!"llvm.loop.unroll.disable"}
diff --git a/llvm/test/Transforms/LoopDistribute/disable_nonforced_enable.ll b/llvm/test/Transforms/LoopDistribute/disable_nonforced_enable.ll
index 45a2d31256a27..e8646ea357448 100644
--- a/llvm/test/Transforms/LoopDistribute/disable_nonforced_enable.ll
+++ b/llvm/test/Transforms/LoopDistribute/disable_nonforced_enable.ll
@@ -43,4 +43,4 @@ for.end:
   ret void
 }
 
-!0 = distinct !{!0, !{!"llvm.loop.disable_nonforced"}, !{!"llvm.loop.distribute.enable", i32 1}}
+!0 = distinct !{!0, !{!"llvm.loop.disable_nonforced"}, !{!"llvm.loop.distribute.enable", i1 1}}
diff --git a/llvm/test/Transforms/LoopDistribute/malformed-metadata.ll b/llvm/test/Transforms/LoopDistribute/malformed-metadata.ll
new file mode 100644
index 0000000000000..0c2764d0c4982
--- /dev/null
+++ b/llvm/test/Transforms/LoopDistribute/malformed-metadata.ll
@@ -0,0 +1,34 @@
+; RUN: not opt -passes=verify -S < %s 2>&1 | FileCheck %s
+;
+; CHECK: llvm.loop boolean attribute must have exactly two operands
+
+define void @test(ptr nocapture %A, ptr nocapture readonly %B, i32 %Length) {
+entry:
+  %cmp9 = icmp sgt i32 %Length, 0
+  br i1 %cmp9, label %for.body.preheader, label %for.end
+
+for.body.preheader:
+  br label %for.body
+
+for.body:
+  %indvars.iv = phi i64 [ %indvars.iv.next, %for.body ], [ 0, %for.body.preheader ]
+  %arrayidx = getelementptr inbounds i32, ptr %B, i64 %indvars.iv
+  %0 = load i32, ptr %arrayidx, align 4
+  %idxprom1 = sext i32 %0 to i64
+  %arrayidx2 = getelementptr inbounds i32, ptr %A, i64 %idxprom1
+  %1 = load i32, ptr %arrayidx2, align 4
+  %arrayidx4 = getelementptr inbounds i32, ptr %A, i64 %indvars.iv
+  store i32 %1, ptr %arrayidx4, align 4
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %lftr.wideiv = trunc i64 %indvars.iv.next to i32
+  %exitcond = icmp eq i32 %lftr.wideiv, %Length
+  br i1 %exitcond, label %for.end.loopexit, label %for.body, !llvm.loop !50
+
+for.end.loopexit:
+  br label %for.end
+
+for.end:
+  ret void
+}
+
+!50 = !{!50, !{!"llvm.loop.distribute.enable"}}
diff --git a/llvm/test/Transforms/LoopTransformWarning/distribution-remarks-missed.ll b/llvm/test/Transforms/LoopTransformWarning/distribution-remarks-missed.ll
index 2d99f4acac468..ebe2ff4079298 100644
--- a/llvm/test/Transforms/LoopTransformWarning/distribution-remarks-missed.ll
+++ b/llvm/test/Transforms/LoopTransformWarning/distribution-remarks-missed.ll
@@ -90,4 +90,4 @@ for.end:
 !45 = !DILocation(line: 27, column: 21, scope: !37)
 !46 = distinct !DISubprogram(name: "test_multiple_failures", line: 26, isLocal: false, isDefinition: true, virtualIndex: 6, flags: DIFlagPrototyped, isOptimized: true, unit: !0, scopeLine: 26, file: !1, scope: !5, type: !6, retainedNodes: !2)
 
-!50 = !{!50, !{!"llvm.loop.distribute.enable"}}
+!50 = !{!50, !{!"llvm.loop.distribute.enable", i1 true}}
diff --git a/llvm/test/Transforms/LoopTransformWarning/optnone.ll b/llvm/test/Transforms/LoopTransformWarning/optnone.ll
index 469f3ce7ffc2e..1080165203427 100644
--- a/llvm/test/Transforms/LoopTransformWarning/optnone.ll
+++ b/llvm/test/Transforms/LoopTransformWarning/optnone.ll
@@ -38,9 +38,9 @@ attributes #0 = { noinline optnone }
 
 !0 = distinct !{!0, !1, !2, !3}
 !1 = !{!"llvm.loop.unroll.enable"}
-!2 = !{!"llvm.loop.distribute.enable"}
+!2 = !{!"llvm.loop.distribute.enable", i1 1}
 !3 = !{!"llvm.loop.unroll_and_jam.enable"}
-!4 = !{!"llvm.loop.vectorize.enable", i1 true}
+!4 = !{!"llvm.loop.vectorize.enable", i1 1}
 
 
 ; CHECK-NOT: warning
diff --git a/llvm/test/Transforms/LoopUnroll/malformed-metadata.ll b/llvm/test/Transforms/LoopUnroll/malformed-metadata.ll
new file mode 100644
index 0000000000000..71a0e0665945e
--- /dev/null
+++ b/llvm/test/Transforms/LoopUnroll/malformed-metadata.ll
@@ -0,0 +1,17 @@
+; RUN: not opt -passes=verify -S < %s 2>&1 | FileCheck %s
+;
+; CHECK: llvm.loop attribute must have exactly one operand
+
+define void @test(ptr nocapture %A, i32 %N) {
+entry:
+  br label %loop
+loop:
+  %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+  %i.next = add i32 %i, 1
+  %cond = icmp slt i32 %i.next, %N
+  br i1 %cond, label %loop, label %exit, !llvm.loop !0
+exit:
+  ret void
+}
+
+!0 = !{!0, !{!"llvm.loop.unroll.disable", i1 0}}
diff --git a/llvm/test/Transforms/LoopUnrollAndJam/malformed-metadata.ll b/llvm/test/Transforms/LoopUnrollAndJam/malformed-metadata.ll
new file mode 100644
index 0000000000000..ea43d755a09dd
--- /dev/null
+++ b/llvm/test/Transforms/LoopUnrollAndJam/malformed-metadata.ll
@@ -0,0 +1,17 @@
+; RUN: not opt -passes=verify -S < %s 2>&1 | FileCheck %s
+;
+; CHECK: llvm.loop attribute must have exactly one operand
+
+define void @test(ptr nocapture %A, i32 %N) {
+entry:
+  br label %loop
+loop:
+  %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+  %i.next = add i32 %i, 1
+  %cond = icmp slt i32 %i.next, %N
+  br i1 %cond, label %loop, label %exit, !llvm.loop !0
+exit:
+  ret void
+}
+
+!0 = !{!0, !{!"llvm.loop.unroll_and_jam.enable", i1 1}}
diff --git a/llvm/test/Transforms/LoopVectorize/disable_nonforced_enable.ll b/llvm/test/Transforms/LoopVectorize/disable_nonforced_enable.ll
index 8c4e50fdd7a61..1ba9574684864 100644
--- a/llvm/test/Transforms/LoopVectorize/disable_nonforced_enable.ll
+++ b/llvm/test/Transforms/LoopVectorize/disable_nonforced_enable.ll
@@ -26,4 +26,4 @@ for.end:
   ret void
 }
 
-!0 = !{!0, !{!"llvm.loop.disable_nonforced"}, !{!"llvm.loop.vectorize.enable", i32 1}}
+!0 = !{!0, !{!"llvm.loop.disable_nonforced"}, !{!"llvm.loop.vectorize.enable", i1 true}}
diff --git a/llvm/test/Transforms/LoopVectorize/remove_metadata.ll b/llvm/test/Transforms/LoopVectorize/remove_metadata.ll
index 96de172a0ea7c..464da1704e7f6 100644
--- a/llvm/test/Transforms/LoopVectorize/remove_metadata.ll
+++ b/llvm/test/Transforms/LoopVectorize/remove_metadata.ll
@@ -25,7 +25,7 @@ for.end:
   ret void
 }
 
-!0 = !{!0, !{!"llvm.loop.vectorize.some_property"}, !{!"llvm.loop.vectorize.enable", i32 1}}
+!0 = !{!0, !{!"llvm.loop.vectorize.some_property"}, !{!"llvm.loop.vectorize.enable", i1 true}}
 
 ; CHECK-NOT: llvm.loop.vectorize.
 ; CHECK: {!"llvm.loop.isvectorized", i32 1}

``````````

</details>


https://github.com/llvm/llvm-project/pull/182252


More information about the llvm-commits mailing list