[llvm] IR: Add verifier checks and LangRef for llvm.loop.align (PR #221933)

Matt Arsenault via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 11 12:49:56 PDT 2026


https://github.com/arsenm updated https://github.com/llvm/llvm-project/pull/221933

>From c501cea6157869b9b6e31df9342c48104623e77a Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Sun, 6 Sep 2026 21:58:00 +0200
Subject: [PATCH 1/2] IR: Add verifier checks and LangRef for llvm.loop.align

Verify the nested !{!"llvm.loop.align", i32 N} tag.
Require exactly two operands, an integer constant of type i32 or smaller,
and a positive power-of-two value

Co-authored-by: Claude (Claude-Opus-4.8)
---
 llvm/docs/LangRef.md                       |  15 +++
 llvm/lib/CodeGen/MachineBlockPlacement.cpp |   3 -
 llvm/lib/IR/Verifier.cpp                   |  27 +++++
 llvm/test/Assembler/llvm.loop.align.ll     |  28 +++++
 llvm/test/Verifier/llvm.loop.align.ll      | 121 +++++++++++++++++++++
 5 files changed, 191 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/Assembler/llvm.loop.align.ll
 create mode 100644 llvm/test/Verifier/llvm.loop.align.ll

diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 14caff88243c1..1dd342e256b43 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -8631,6 +8631,21 @@ via a volatile memory access, I/O, or other synchronization. If such a loop is
 not found to interact with the environment in an observable way, the loop may
 be removed. This corresponds to the `mustprogress` function attribute.
 
+#### '`llvm.loop.align`' Metadata
+
+This metadata suggests an alignment (in bytes) for the loop to the backend. The
+first operand is the string `llvm.loop.align` and the second operand is a
+positive power-of-two integer constant of type `i32` specifying the alignment.
+For example:
+
+```llvm
+!0 = !{!"llvm.loop.align", i32 64}
+```
+
+The backend aligns the loop to the maximum of this value and the target's
+preferred loop alignment. This corresponds to the Clang `[[clang::code_align(N)]]`
+statement attribute.
+
 #### '`irr_loop`' Metadata
 
 `irr_loop` metadata may be attached to the terminator instruction of a basic
diff --git a/llvm/lib/CodeGen/MachineBlockPlacement.cpp b/llvm/lib/CodeGen/MachineBlockPlacement.cpp
index 1378aaa67ba39..a748150d98c64 100644
--- a/llvm/lib/CodeGen/MachineBlockPlacement.cpp
+++ b/llvm/lib/CodeGen/MachineBlockPlacement.cpp
@@ -3044,11 +3044,8 @@ void MachineBlockPlacement::alignBlocks() {
         if (S == nullptr)
           continue;
         if (S->getString() == "llvm.loop.align") {
-          assert(MD->getNumOperands() == 2 &&
-                 "per-loop align metadata should have two operands.");
           MDAlign =
               mdconst::extract<ConstantInt>(MD->getOperand(1))->getZExtValue();
-          assert(MDAlign >= 1 && "per-loop align value must be positive.");
         }
       }
     }
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 12ab6e9c34a5e..f1b377798a4d9 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -1066,6 +1066,13 @@ void Verifier::visitMDNode(const MDNode &BaseMD,
       }
     }
 
+    // FIXME: The nested llvm.loop.* property tags (llvm.loop.align,
+    // llvm.loop.estimated_trip_count, the boolean enable/disable tags below)
+    // are only meaningful as operands of an llvm.loop node. Neither llvm.loop's
+    // structure nor the requirement that these tags appear only within it is
+    // validated here; the checks below fire on any matching tuple regardless of
+    // where it appears.
+
     // Check llvm.loop.estimated_trip_count.
     if (CurrentMD->getNumOperands() > 0 &&
         CurrentMD->getOperand(0).equalsStr(LLVMLoopEstimatedTripCount)) {
@@ -1080,6 +1087,26 @@ void Verifier::visitMDNode(const MDNode &BaseMD,
             CurrentMD);
     }
 
+    // Check llvm.loop.align.
+    if (CurrentMD->getNumOperands() > 0 &&
+        CurrentMD->getOperand(0).equalsStr("llvm.loop.align")) {
+      Check(CurrentMD->getNumOperands() == 2, "Expected two operands",
+            CurrentMD);
+      auto *AlignMD =
+          mdconst::dyn_extract_or_null<ConstantInt>(CurrentMD->getOperand(1));
+      Check(AlignMD && AlignMD->getType()->isIntegerTy(32),
+            "Expected the alignment to be an integer constant of type i32",
+            CurrentMD);
+      if (AlignMD) {
+        uint64_t Align = AlignMD->getValue().getZExtValue();
+        Check(isPowerOf2_64(Align),
+              "Expected the alignment to be a power of two", CurrentMD);
+        Check(Align <= Value::MaximumAlignment,
+              "Alignment is larger than the implementation defined limit",
+              CurrentMD);
+      }
+    }
+
     // Enforce the single-operand form of the loop enable/disable pairs.
     if (CurrentMD->getNumOperands() > 0 &&
         any_of(OldBooleanLoopTags, [CurrentMD](const BooleanLoopTags &Tags) {
diff --git a/llvm/test/Assembler/llvm.loop.align.ll b/llvm/test/Assembler/llvm.loop.align.ll
new file mode 100644
index 0000000000000..fab172dab47ed
--- /dev/null
+++ b/llvm/test/Assembler/llvm.loop.align.ll
@@ -0,0 +1,28 @@
+; RUN: llvm-as < %s | llvm-dis | FileCheck %s
+
+; Valid "llvm.loop.align" metadata round-trips through the assembler.
+
+define void @pow2() {
+  br label %body
+body:
+  br i1 0, label %body, label %exit, !llvm.loop !0
+exit:
+  ret void
+}
+!0 = distinct !{!0, !1}
+!1 = !{!"llvm.loop.align", i32 64}
+
+define void @one() {
+  br label %body
+body:
+  br i1 0, label %body, label %exit, !llvm.loop !2
+exit:
+  ret void
+}
+!2 = distinct !{!2, !3}
+!3 = !{!"llvm.loop.align", i32 1}
+
+; CHECK: [[LOOP0:![0-9]+]] = distinct !{[[LOOP0]], [[ALIGN0:![0-9]+]]}
+; CHECK: [[ALIGN0]] = !{!"llvm.loop.align", i32 64}
+; CHECK: [[LOOP2:![0-9]+]] = distinct !{[[LOOP2]], [[ALIGN2:![0-9]+]]}
+; CHECK: [[ALIGN2]] = !{!"llvm.loop.align", i32 1}
diff --git a/llvm/test/Verifier/llvm.loop.align.ll b/llvm/test/Verifier/llvm.loop.align.ll
new file mode 100644
index 0000000000000..5cd7d2849f8e5
--- /dev/null
+++ b/llvm/test/Verifier/llvm.loop.align.ll
@@ -0,0 +1,121 @@
+; Test "llvm.loop.align" validation
+
+; RUN: split-file %s %t
+
+; RUN: not llvm-as < %t/too-few.ll 2>&1 | FileCheck %s --check-prefix=TOO-FEW
+; RUN: not llvm-as < %t/too-many.ll 2>&1 | FileCheck %s --check-prefix=TOO-MANY
+
+; RUN: not llvm-as < %t/i16.ll 2>&1 | FileCheck %s --check-prefix=BAD-VALUE
+; RUN: not llvm-as < %t/i64.ll 2>&1 | FileCheck %s --check-prefix=BAD-VALUE
+; RUN: not llvm-as < %t/mdstring.ll 2>&1 | FileCheck %s --check-prefix=BAD-VALUE
+; RUN: not llvm-as < %t/mdnode.ll 2>&1 | FileCheck %s --check-prefix=BAD-VALUE
+
+; RUN: not llvm-as < %t/zero.ll 2>&1 | FileCheck %s --check-prefix=BAD-ALIGN
+; RUN: not llvm-as < %t/not-pow2.ll 2>&1 | FileCheck %s --check-prefix=BAD-ALIGN
+; RUN: not llvm-as < %t/negative.ll 2>&1 | FileCheck %s --check-prefix=BAD-ALIGN
+
+;--- too-few.ll
+define void @test() {
+  br label %body
+body:
+  br i1 0, label %body, label %exit, !llvm.loop !0
+exit:
+  ret void
+}
+!0 = distinct !{!0, !1}
+!1 = !{!"llvm.loop.align"}
+; TOO-FEW: Expected two operands
+; TOO-FEW: !{!"llvm.loop.align"}
+
+;--- too-many.ll
+define void @test() {
+  br label %body
+body:
+  br i1 0, label %body, label %exit, !llvm.loop !0
+exit:
+  ret void
+}
+!0 = distinct !{!0, !1}
+!1 = !{!"llvm.loop.align", i32 64, i32 64}
+; TOO-MANY: Expected two operands
+; TOO-MANY: !{!"llvm.loop.align", i32 64, i32 64}
+
+;--- i16.ll
+define void @test() {
+  br label %body
+body:
+  br i1 0, label %body, label %exit, !llvm.loop !0
+exit:
+  ret void
+}
+!0 = distinct !{!0, !1}
+!1 = !{!"llvm.loop.align", i16 16}
+; BAD-VALUE: Expected the alignment to be an integer constant of type i32
+
+;--- i64.ll
+define void @test() {
+  br label %body
+body:
+  br i1 0, label %body, label %exit, !llvm.loop !0
+exit:
+  ret void
+}
+!0 = distinct !{!0, !1}
+!1 = !{!"llvm.loop.align", i64 64}
+
+;--- mdstring.ll
+define void @test() {
+  br label %body
+body:
+  br i1 0, label %body, label %exit, !llvm.loop !0
+exit:
+  ret void
+}
+!0 = distinct !{!0, !1}
+!1 = !{!"llvm.loop.align", !"64"}
+
+;--- mdnode.ll
+define void @test() {
+  br label %body
+body:
+  br i1 0, label %body, label %exit, !llvm.loop !0
+exit:
+  ret void
+}
+!0 = distinct !{!0, !1}
+!1 = !{!"llvm.loop.align", !2}
+!2 = !{i32 64}
+
+;--- zero.ll
+define void @test() {
+  br label %body
+body:
+  br i1 0, label %body, label %exit, !llvm.loop !0
+exit:
+  ret void
+}
+!0 = distinct !{!0, !1}
+!1 = !{!"llvm.loop.align", i32 0}
+; BAD-ALIGN: Expected the alignment to be a power of two
+
+;--- not-pow2.ll
+define void @test() {
+  br label %body
+body:
+  br i1 0, label %body, label %exit, !llvm.loop !0
+exit:
+  ret void
+}
+!0 = distinct !{!0, !1}
+!1 = !{!"llvm.loop.align", i32 3}
+
+;--- negative.ll
+define void @test() {
+  br label %body
+body:
+  br i1 0, label %body, label %exit, !llvm.loop !0
+exit:
+  ret void
+}
+!0 = distinct !{!0, !1}
+!1 = !{!"llvm.loop.align", i32 -8}

>From 3f3a4b3fca613c16a7915568aeeb0cbd2df7f65f Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Fri, 11 Sep 2026 20:31:02 +0200
Subject: [PATCH 2/2] Address comments

---
 llvm/test/Verifier/llvm.loop.align.ll | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)

diff --git a/llvm/test/Verifier/llvm.loop.align.ll b/llvm/test/Verifier/llvm.loop.align.ll
index 5cd7d2849f8e5..01bdc05419812 100644
--- a/llvm/test/Verifier/llvm.loop.align.ll
+++ b/llvm/test/Verifier/llvm.loop.align.ll
@@ -5,14 +5,14 @@
 ; RUN: not llvm-as < %t/too-few.ll 2>&1 | FileCheck %s --check-prefix=TOO-FEW
 ; RUN: not llvm-as < %t/too-many.ll 2>&1 | FileCheck %s --check-prefix=TOO-MANY
 
-; RUN: not llvm-as < %t/i16.ll 2>&1 | FileCheck %s --check-prefix=BAD-VALUE
-; RUN: not llvm-as < %t/i64.ll 2>&1 | FileCheck %s --check-prefix=BAD-VALUE
-; RUN: not llvm-as < %t/mdstring.ll 2>&1 | FileCheck %s --check-prefix=BAD-VALUE
-; RUN: not llvm-as < %t/mdnode.ll 2>&1 | FileCheck %s --check-prefix=BAD-VALUE
+; RUN: not llvm-as < %t/i16.ll 2>&1 | FileCheck %s --check-prefix=I16
+; RUN: not llvm-as < %t/i64.ll 2>&1 | FileCheck %s --check-prefix=I64
+; RUN: not llvm-as < %t/mdstring.ll 2>&1 | FileCheck %s --check-prefix=MDSTRING
+; RUN: not llvm-as < %t/mdnode.ll 2>&1 | FileCheck %s --check-prefix=MDNODE
 
-; RUN: not llvm-as < %t/zero.ll 2>&1 | FileCheck %s --check-prefix=BAD-ALIGN
-; RUN: not llvm-as < %t/not-pow2.ll 2>&1 | FileCheck %s --check-prefix=BAD-ALIGN
-; RUN: not llvm-as < %t/negative.ll 2>&1 | FileCheck %s --check-prefix=BAD-ALIGN
+; RUN: not llvm-as < %t/zero.ll 2>&1 | FileCheck %s --check-prefix=ZERO
+; RUN: not llvm-as < %t/not-pow2.ll 2>&1 | FileCheck %s --check-prefix=NOT-POW2
+; RUN: not llvm-as < %t/negative.ll 2>&1 | FileCheck %s --check-prefix=NEGATIVE
 
 ;--- too-few.ll
 define void @test() {
@@ -50,7 +50,7 @@ exit:
 }
 !0 = distinct !{!0, !1}
 !1 = !{!"llvm.loop.align", i16 16}
-; BAD-VALUE: Expected the alignment to be an integer constant of type i32
+; I16: Expected the alignment to be an integer constant of type i32
 
 ;--- i64.ll
 define void @test() {
@@ -62,6 +62,7 @@ exit:
 }
 !0 = distinct !{!0, !1}
 !1 = !{!"llvm.loop.align", i64 64}
+; I64: Expected the alignment to be an integer constant of type i32
 
 ;--- mdstring.ll
 define void @test() {
@@ -73,6 +74,7 @@ exit:
 }
 !0 = distinct !{!0, !1}
 !1 = !{!"llvm.loop.align", !"64"}
+; MDSTRING: Expected the alignment to be an integer constant of type i32
 
 ;--- mdnode.ll
 define void @test() {
@@ -85,6 +87,7 @@ exit:
 !0 = distinct !{!0, !1}
 !1 = !{!"llvm.loop.align", !2}
 !2 = !{i32 64}
+; MDNODE: Expected the alignment to be an integer constant of type i32
 
 ;--- zero.ll
 define void @test() {
@@ -96,7 +99,7 @@ exit:
 }
 !0 = distinct !{!0, !1}
 !1 = !{!"llvm.loop.align", i32 0}
-; BAD-ALIGN: Expected the alignment to be a power of two
+; ZERO: Expected the alignment to be a power of two
 
 ;--- not-pow2.ll
 define void @test() {
@@ -108,6 +111,7 @@ exit:
 }
 !0 = distinct !{!0, !1}
 !1 = !{!"llvm.loop.align", i32 3}
+; NOT-POW2: Expected the alignment to be a power of two
 
 ;--- negative.ll
 define void @test() {
@@ -119,3 +123,4 @@ exit:
 }
 !0 = distinct !{!0, !1}
 !1 = !{!"llvm.loop.align", i32 -8}
+; NEGATIVE: Expected the alignment to be a power of two



More information about the llvm-commits mailing list