[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