[llvm] [IR] Add llvm.raw.sections named metadata (PR #226954)
Steffen Larsen via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 28 08:59:44 PDT 2026
https://github.com/steffenlarsen updated https://github.com/llvm/llvm-project/pull/226954
>From d3f17a51cac72deecfcee908550070373eeaf56b Mon Sep 17 00:00:00 2001
From: Steffen Holst Larsen <sholstla at amd.com>
Date: Mon, 28 Sep 2026 05:28:30 -0500
Subject: [PATCH 1/2] [IR] Add llvm.raw.sections named metadata
Add a module-level `!llvm.raw.sections` named metadata node for
embedding arbitrary binary data into named sections of the output object
file. Each entry is a tuple of {section name, alignment, flags, data}.
The flags are a target-independent bitmask (Alloc, Write, Exec, Exclude)
that each object file format maps onto its own section flags.
The AsmPrinter lowers the entries directly at the MC layer through a new
TargetLoweringObjectFile::getNamedSection hook, implemented for ELF,
Mach-O, COFF and Wasm. Formats without the hook skip the entries. Unlike
a GlobalVariable, the data is not subject to the size limits of the
target's address spaces.
The verifier checks the shape of each entry and rejects invalid flag
combinations.
Assisted-by: Claude Opus 4.6
Signed-off-by: Steffen Holst Larsen <sholstla at amd.com>
---
llvm/docs/LangRef.md | 56 +++++++++++++++++++
.../CodeGen/TargetLoweringObjectFileImpl.h | 8 +++
llvm/include/llvm/IR/Module.h | 24 ++++++++
.../llvm/Target/TargetLoweringObjectFile.h | 7 +++
llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp | 40 +++++++++++++
.../CodeGen/TargetLoweringObjectFileImpl.cpp | 27 +++++++++
llvm/lib/IR/Module.cpp | 21 +++++++
llvm/lib/IR/Verifier.cpp | 38 +++++++++++++
.../CodeGen/WebAssembly/raw-sections-wasm.ll | 7 +++
llvm/test/CodeGen/X86/raw-sections-coff.ll | 12 ++++
llvm/test/CodeGen/X86/raw-sections-elf.ll | 7 +++
llvm/test/CodeGen/X86/raw-sections-macho.ll | 9 +++
llvm/test/Verifier/raw-sections-flags.ll | 31 ++++++++++
llvm/test/Verifier/raw-sections.ll | 9 +++
14 files changed, 296 insertions(+)
create mode 100644 llvm/test/CodeGen/WebAssembly/raw-sections-wasm.ll
create mode 100644 llvm/test/CodeGen/X86/raw-sections-coff.ll
create mode 100644 llvm/test/CodeGen/X86/raw-sections-elf.ll
create mode 100644 llvm/test/CodeGen/X86/raw-sections-macho.ll
create mode 100644 llvm/test/Verifier/raw-sections-flags.ll
create mode 100644 llvm/test/Verifier/raw-sections.ll
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 14caff88243c1..81650a2a59dca 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -9684,6 +9684,62 @@ for an integer access:
Multiple TBAA operands are allowed to support merging of modules that may use
different TBAA hierarchies (e.g., when mixing C and C++).
+## '`llvm.raw.sections`' Named Metadata
+
+The module-level `!llvm.raw.sections` metadata allows embedding arbitrary
+binary data into named sections of the output object file. A
+`!llvm.raw.sections` metadata node is a list of metadata nodes with the
+following fields:
+
+```
+!0 = !{!"section_name", i32 alignment, i32 flags, !"raw data"}
+```
+
+- **section_name**: The name of the output section.
+- **alignment**: The byte alignment of the section data.
+- **flags**: A bitmask describing the properties of the section. Each target
+ maps this onto format-appropriate section flags. The supported bits are
+ listed below.
+- **raw data**: The binary contents of the section.
+
+```{list-table}
+:header-rows: 1
+
+* - Value
+ - Name
+ - Description
+
+* - `0x1`
+ - Alloc
+ - The section occupies memory at load time.
+
+* - `0x2`
+ - Write
+ - The section is writable. Requires `Alloc`.
+
+* - `0x4`
+ - Exec
+ - The section is executable. Requires `Alloc`.
+
+* - `0x8`
+ - Exclude
+ - The section is dropped from the final link. Must not be combined with any
+ other bit.
+```
+
+A flags value of `0` describes a section that is present in the object file
+but is not loaded into memory. `Write` and `Exec` must not be combined.
+
+Example:
+```
+!llvm.raw.sections = !{!0}
+!0 = !{!"__mydata", i32 8, i32 1, !"\DE\AD\BE\EF"}
+```
+
+Each of the nodes inside a `!llvm.raw.sections` metadata node gets lowered to
+a target-specific named data section. Targets that do not support arbitrary
+named sections silently skip `!llvm.raw.sections` metadata nodes.
+
(summary)=
## ThinLTO Summary
diff --git a/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h b/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
index af6eb0269378a..f3b451e22a2d4 100644
--- a/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
+++ b/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
@@ -54,6 +54,8 @@ class LLVM_ABI TargetLoweringObjectFileELF : public TargetLoweringObjectFile {
/// Emit Obj-C garbage collection and linker options.
void emitModuleMetadata(MCStreamer &Streamer, Module &M) const override;
+ MCSection *getNamedSection(StringRef Name, SectionKind Kind) const override;
+
void emitPersonalityValue(MCStreamer &Streamer, const DataLayout &DL,
const MCSymbol *Sym,
const MachineModuleInfo *MMI) const override;
@@ -149,6 +151,8 @@ class LLVM_ABI TargetLoweringObjectFileMachO : public TargetLoweringObjectFile {
/// Emit the module flags that specify the garbage collection information.
void emitModuleMetadata(MCStreamer &Streamer, Module &M) const override;
+ MCSection *getNamedSection(StringRef Name, SectionKind Kind) const override;
+
void emitLinkerDirectives(MCStreamer &Streamer, Module &M) const override;
MCSection *SelectSectionForGlobal(const GlobalObject *GO, SectionKind Kind,
@@ -212,6 +216,8 @@ class LLVM_ABI TargetLoweringObjectFileCOFF : public TargetLoweringObjectFile {
/// Emit Obj-C garbage collection and linker options.
void emitModuleMetadata(MCStreamer &Streamer, Module &M) const override;
+ MCSection *getNamedSection(StringRef Name, SectionKind Kind) const override;
+
void emitLinkerDirectives(MCStreamer &Streamer, Module &M) const override;
MCSection *getStaticCtorSection(unsigned Priority,
@@ -250,6 +256,8 @@ class LLVM_ABI TargetLoweringObjectFileWasm : public TargetLoweringObjectFile {
bool shouldPutJumpTableInFunctionSection(bool UsesLabelDifference,
const Function &F) const override;
+ MCSection *getNamedSection(StringRef Name, SectionKind Kind) const override;
+
void InitializeWasm();
MCSection *getStaticCtorSection(unsigned Priority,
const MCSymbol *KeySym) const override;
diff --git a/llvm/include/llvm/IR/Module.h b/llvm/include/llvm/IR/Module.h
index 2fb7eff6daa2a..c96504f8c416b 100644
--- a/llvm/include/llvm/IR/Module.h
+++ b/llvm/include/llvm/IR/Module.h
@@ -161,6 +161,30 @@ class LLVM_ABI Module {
/// converted result in MFB.
static bool isValidModFlagBehavior(Metadata *MD, ModFlagBehavior &MFB);
+ /// Flags describing the properties of a section declared via the
+ /// `llvm.raw.sections` named metadata. Targets map these onto
+ /// format-specific section flags.
+ enum RawSectionFlags {
+ /// The section occupies memory at load time.
+ RawSectionAlloc = 1 << 0,
+
+ /// The section is writable at runtime. Requires RawSectionAlloc.
+ RawSectionWrite = 1 << 1,
+
+ /// The section is executable. Requires RawSectionAlloc.
+ RawSectionExec = 1 << 2,
+
+ /// The section is excluded from the final link. Must not be combined
+ /// with any other flag.
+ RawSectionExclude = 1 << 3,
+
+ RawSectionFlagsMask = RawSectionAlloc | RawSectionWrite | RawSectionExec |
+ RawSectionExclude
+ };
+
+ /// Checks whether \p Flags is a valid combination of RawSectionFlags.
+ static bool isValidRawSectionFlags(uint64_t Flags);
+
struct ModuleFlagEntry {
ModFlagBehavior Behavior;
MDString *Key;
diff --git a/llvm/include/llvm/Target/TargetLoweringObjectFile.h b/llvm/include/llvm/Target/TargetLoweringObjectFile.h
index 3cce5974e6705..437279501af53 100644
--- a/llvm/include/llvm/Target/TargetLoweringObjectFile.h
+++ b/llvm/include/llvm/Target/TargetLoweringObjectFile.h
@@ -90,6 +90,13 @@ class LLVM_ABI TargetLoweringObjectFile : public MCObjectFileInfo {
/// Emit the module-level metadata that the platform cares about.
virtual void emitModuleMetadata(MCStreamer &Streamer, Module &M) const {}
+ /// Get a section with the given name and kind, using format-appropriate
+ /// defaults for section flags.
+ /// Returns nullptr if not supported by this object file format.
+ virtual MCSection *getNamedSection(StringRef Name, SectionKind Kind) const {
+ return nullptr;
+ }
+
/// Emit Call Graph Profile metadata.
void emitCGProfileMetadata(MCStreamer &Streamer, Module &M) const;
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index 3d06677d1706b..184eceebfc311 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -2920,6 +2920,24 @@ static void removeMemtagFromGlobal(GlobalVariable &G) {
G.setSanitizerMetadata(Meta);
}
+/// Map the target-independent flags of an `llvm.raw.sections` entry onto the
+/// SectionKind that targets use to derive format-specific section flags.
+/// The flag combination is validated by the IR verifier.
+static SectionKind getKindForRawSectionFlags(uint64_t Flags) {
+ assert(Module::isValidRawSectionFlags(Flags) &&
+ "invalid llvm.raw.sections flags should be rejected by the verifier");
+
+ if (Flags & Module::RawSectionExclude)
+ return SectionKind::getExclude();
+ if (!(Flags & Module::RawSectionAlloc))
+ return SectionKind::getMetadata();
+ if (Flags & Module::RawSectionExec)
+ return SectionKind::getText();
+ if (Flags & Module::RawSectionWrite)
+ return SectionKind::getData();
+ return SectionKind::getReadOnly();
+}
+
bool AsmPrinter::doFinalization(Module &M) {
// Set the MachineFunction to nullptr so that we can catch attempted
// accesses to MF specific features at the module level and so that
@@ -2998,6 +3016,28 @@ bool AsmPrinter::doFinalization(Module &M) {
TLOF.emitModuleMetadata(*OutStreamer, M);
+ // Emit raw section data from llvm.raw.sections metadata. Each operand is
+ // a tuple of {section_name, alignment, flags, data}.
+ if (const NamedMDNode *RawSections =
+ M.getNamedMetadata("llvm.raw.sections")) {
+ for (const MDNode *Op : RawSections->operands()) {
+ auto *SectionName = cast<MDString>(Op->getOperand(0));
+ auto *AlignCI = mdconst::extract<ConstantInt>(Op->getOperand(1));
+ auto *FlagsCI = mdconst::extract<ConstantInt>(Op->getOperand(2));
+ auto *Data = cast<MDString>(Op->getOperand(3));
+
+ SectionKind Kind = getKindForRawSectionFlags(FlagsCI->getZExtValue());
+ if (MCSection *Section =
+ TLOF.getNamedSection(SectionName->getString(), Kind)) {
+ OutStreamer->pushSection();
+ OutStreamer->switchSection(Section);
+ OutStreamer->emitValueToAlignment(Align(AlignCI->getZExtValue()));
+ OutStreamer->emitBytes(Data->getString());
+ OutStreamer->popSection();
+ }
+ }
+ }
+
if (Target.isOSBinFormatELF()) {
MachineModuleInfoELF &MMIELF = MMI->getObjFileInfo<MachineModuleInfoELF>();
diff --git a/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp b/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
index 4643ef995afec..dc7c460f5e978 100644
--- a/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
+++ b/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
@@ -571,6 +571,14 @@ static unsigned getELFSectionFlags(SectionKind K, const Triple &T) {
return Flags;
}
+MCSection *
+TargetLoweringObjectFileELF::getNamedSection(StringRef Name,
+ SectionKind Kind) const {
+ unsigned Type = getELFSectionType(Name, Kind);
+ unsigned Flags = getELFSectionFlags(Kind, getContext().getTargetTriple());
+ return getContext().getELFSection(Name, Type, Flags);
+}
+
static const Comdat *getELFComdat(const GlobalValue *GV) {
const Comdat *C = GV->getComdat();
if (!C)
@@ -1367,6 +1375,13 @@ void TargetLoweringObjectFileMachO::emitModuleMetadata(MCStreamer &Streamer,
Streamer.addBlankLine();
}
+MCSection *
+TargetLoweringObjectFileMachO::getNamedSection(StringRef Name,
+ SectionKind Kind) const {
+ auto [Segment, SecName] = Name.split(',');
+ return getContext().getMachOSection(Segment, SecName, 0, Kind);
+}
+
void TargetLoweringObjectFileMachO::emitLinkerDirectives(MCStreamer &Streamer,
Module &M) const {
if (auto *LinkerOptions = M.getNamedMetadata("llvm.linker.options")) {
@@ -1961,6 +1976,12 @@ void TargetLoweringObjectFileCOFF::emitModuleMetadata(MCStreamer &Streamer,
});
}
+MCSection *
+TargetLoweringObjectFileCOFF::getNamedSection(StringRef Name,
+ SectionKind Kind) const {
+ return getContext().getCOFFSection(Name, getCOFFSectionFlags(Kind, *TM));
+}
+
void TargetLoweringObjectFileCOFF::emitLinkerDirectives(
MCStreamer &Streamer, Module &M) const {
if (NamedMDNode *LinkerOptions = M.getNamedMetadata("llvm.linker.options")) {
@@ -2275,6 +2296,12 @@ void TargetLoweringObjectFileWasm::getModuleMetadata(Module &M) {
Used.insert(GO);
}
+MCSection *
+TargetLoweringObjectFileWasm::getNamedSection(StringRef Name,
+ SectionKind Kind) const {
+ return getContext().getWasmSection(Name, Kind);
+}
+
MCSection *TargetLoweringObjectFileWasm::getExplicitSectionGlobal(
const GlobalObject *GO, SectionKind Kind, const TargetMachine &TM) const {
// We don't support explict section names for functions in the wasm object
diff --git a/llvm/lib/IR/Module.cpp b/llvm/lib/IR/Module.cpp
index 510d33c0ad295..8a5f910e8d361 100644
--- a/llvm/lib/IR/Module.cpp
+++ b/llvm/lib/IR/Module.cpp
@@ -337,6 +337,27 @@ bool Module::isValidModFlagBehavior(Metadata *MD, ModFlagBehavior &MFB) {
return false;
}
+bool Module::isValidRawSectionFlags(uint64_t Flags) {
+ if (Flags & ~static_cast<uint64_t>(RawSectionFlagsMask))
+ return false;
+
+ // An excluded section is not present in the output, so it cannot also be
+ // allocated, written or executed.
+ if (Flags & RawSectionExclude)
+ return Flags == RawSectionExclude;
+
+ // Only an allocated section can be written to or executed.
+ if ((Flags & (RawSectionWrite | RawSectionExec)) &&
+ !(Flags & RawSectionAlloc))
+ return false;
+
+ // No section format represents writable executable data.
+ if ((Flags & RawSectionWrite) && (Flags & RawSectionExec))
+ return false;
+
+ return true;
+}
+
/// getModuleFlagsMetadata - Returns the module flags in the provided vector.
void Module::
getModuleFlagsMetadata(SmallVectorImpl<ModuleFlagEntry> &Flags) const {
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 26c34b4221fde..ec03ad1c3c2b4 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -316,6 +316,7 @@ class Verifier : public InstVisitor<Verifier>, VerifierSupport {
visitModuleIdents();
visitModuleCommandLines();
visitModuleErrnoTBAA();
+ visitModuleRawSections();
verifyCompileUnits();
@@ -354,6 +355,7 @@ class Verifier : public InstVisitor<Verifier>, VerifierSupport {
void visitModuleIdents();
void visitModuleCommandLines();
void visitModuleErrnoTBAA();
+ void visitModuleRawSections();
void visitModuleFlags();
void visitModuleFlag(const MDNode *Op,
DenseMap<const MDString *, const MDNode *> &SeenIDs,
@@ -1920,6 +1922,42 @@ void Verifier::visitModuleErrnoTBAA() {
TBAAVerifyHelper.visitTBAAMetadata(nullptr, N);
}
+void Verifier::visitModuleRawSections() {
+ const NamedMDNode *RawSections = M.getNamedMetadata("llvm.raw.sections");
+ if (!RawSections)
+ return;
+
+ for (const MDNode *N : RawSections->operands()) {
+ if (N->getNumOperands() != 4) {
+ CheckFailed("llvm.raw.sections entry must have four operands", N);
+ continue;
+ }
+ Check(dyn_cast_or_null<MDString>(N->getOperand(0)),
+ "llvm.raw.sections entry operand 0 must be a string "
+ "(section name)",
+ N);
+ Check(mdconst::dyn_extract_or_null<ConstantInt>(N->getOperand(1)),
+ "llvm.raw.sections entry operand 1 must be an integer "
+ "(alignment)",
+ N);
+ auto *FlagsCI = mdconst::dyn_extract_or_null<ConstantInt>(N->getOperand(2));
+ Check(FlagsCI,
+ "llvm.raw.sections entry operand 2 must be an integer "
+ "(flags)",
+ N);
+ if (!Module::isValidRawSectionFlags(FlagsCI->getZExtValue())) {
+ CheckFailed("llvm.raw.sections entry operand 2 is not a valid "
+ "combination of section flags",
+ N);
+ continue;
+ }
+ Check(dyn_cast_or_null<MDString>(N->getOperand(3)),
+ "llvm.raw.sections entry operand 3 must be a string "
+ "(section data)",
+ N);
+ }
+}
+
void Verifier::visitModuleFlags() {
const NamedMDNode *Flags = M.getModuleFlagsMetadata();
if (!Flags) return;
diff --git a/llvm/test/CodeGen/WebAssembly/raw-sections-wasm.ll b/llvm/test/CodeGen/WebAssembly/raw-sections-wasm.ll
new file mode 100644
index 0000000000000..166e5dc8b64e3
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/raw-sections-wasm.ll
@@ -0,0 +1,7 @@
+; RUN: llc -filetype=obj -mtriple=wasm32-unknown-unknown %s -o %t.o
+; RUN: llvm-readobj --sections %t.o | FileCheck %s
+
+; CHECK: Name: __clangast
+
+!0 = !{!"__clangast", i32 8, i32 1, !"\de\ad\be\ef"}
+!llvm.raw.sections = !{!0}
diff --git a/llvm/test/CodeGen/X86/raw-sections-coff.ll b/llvm/test/CodeGen/X86/raw-sections-coff.ll
new file mode 100644
index 0000000000000..28b59d516ca3e
--- /dev/null
+++ b/llvm/test/CodeGen/X86/raw-sections-coff.ll
@@ -0,0 +1,12 @@
+; RUN: llc -filetype=obj -mtriple=x86_64-windows-msvc %s -o %t.o
+; RUN: llvm-readobj --sections %t.o | FileCheck %s
+
+; CHECK: Name: clangast
+; CHECK: RawDataSize:
+; CHECK: Characteristics [
+; CHECK-DAG: IMAGE_SCN_CNT_INITIALIZED_DATA
+; CHECK-DAG: IMAGE_SCN_MEM_READ
+; CHECK: ]
+
+!0 = !{!"clangast", i32 8, i32 1, !"\de\ad\be\ef"}
+!llvm.raw.sections = !{!0}
diff --git a/llvm/test/CodeGen/X86/raw-sections-elf.ll b/llvm/test/CodeGen/X86/raw-sections-elf.ll
new file mode 100644
index 0000000000000..a1264e05b1b85
--- /dev/null
+++ b/llvm/test/CodeGen/X86/raw-sections-elf.ll
@@ -0,0 +1,7 @@
+; RUN: llc -filetype=obj -mtriple=x86_64-linux-gnu %s -o %t.o
+; RUN: llvm-readelf --sections %t.o | FileCheck %s
+
+; CHECK: __clangast PROGBITS {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}} 00 A 0 0 8
+
+!0 = !{!"__clangast", i32 8, i32 1, !"\de\ad\be\ef"}
+!llvm.raw.sections = !{!0}
diff --git a/llvm/test/CodeGen/X86/raw-sections-macho.ll b/llvm/test/CodeGen/X86/raw-sections-macho.ll
new file mode 100644
index 0000000000000..46f15bf726ae0
--- /dev/null
+++ b/llvm/test/CodeGen/X86/raw-sections-macho.ll
@@ -0,0 +1,9 @@
+; RUN: llc -filetype=obj -mtriple=x86_64-apple-darwin %s -o %t.o
+; RUN: llvm-readobj --sections %t.o | FileCheck %s
+
+; CHECK: Name: __clangast
+; CHECK-NEXT: Segment: __CLANG
+; CHECK: Size:
+
+!0 = !{!"__CLANG,__clangast", i32 8, i32 1, !"\de\ad\be\ef"}
+!llvm.raw.sections = !{!0}
diff --git a/llvm/test/Verifier/raw-sections-flags.ll b/llvm/test/Verifier/raw-sections-flags.ll
new file mode 100644
index 0000000000000..90a61259a3adb
--- /dev/null
+++ b/llvm/test/Verifier/raw-sections-flags.ll
@@ -0,0 +1,31 @@
+; RUN: not llvm-as < %s -o /dev/null 2>&1 | FileCheck %s
+
+; Write without Alloc.
+; CHECK: llvm.raw.sections entry operand 2 is not a valid combination of section flags
+; CHECK-NEXT: !{!"__mydata", i32 8, i32 2, !"data"}
+
+; Exec without Alloc.
+; CHECK: llvm.raw.sections entry operand 2 is not a valid combination of section flags
+; CHECK-NEXT: !{!"__mydata", i32 8, i32 4, !"data"}
+
+; Write combined with Exec.
+; CHECK: llvm.raw.sections entry operand 2 is not a valid combination of section flags
+; CHECK-NEXT: !{!"__mydata", i32 8, i32 7, !"data"}
+
+; Exclude combined with Alloc.
+; CHECK: llvm.raw.sections entry operand 2 is not a valid combination of section flags
+; CHECK-NEXT: !{!"__mydata", i32 8, i32 9, !"data"}
+
+; Unknown flag bit.
+; CHECK: llvm.raw.sections entry operand 2 is not a valid combination of section flags
+; CHECK-NEXT: !{!"__mydata", i32 8, i32 16, !"data"}
+
+!llvm.raw.sections = !{!0, !1, !2, !3, !4, !5}
+
+; Valid: Alloc only.
+!0 = !{!"__mydata", i32 8, i32 1, !"data"}
+!1 = !{!"__mydata", i32 8, i32 2, !"data"}
+!2 = !{!"__mydata", i32 8, i32 4, !"data"}
+!3 = !{!"__mydata", i32 8, i32 7, !"data"}
+!4 = !{!"__mydata", i32 8, i32 9, !"data"}
+!5 = !{!"__mydata", i32 8, i32 16, !"data"}
diff --git a/llvm/test/Verifier/raw-sections.ll b/llvm/test/Verifier/raw-sections.ll
new file mode 100644
index 0000000000000..1778bb36ddead
--- /dev/null
+++ b/llvm/test/Verifier/raw-sections.ll
@@ -0,0 +1,9 @@
+; RUN: not llvm-as < %s -o /dev/null 2>&1 | FileCheck %s
+
+; CHECK: llvm.raw.sections entry must have four operands
+; CHECK: llvm.raw.sections entry operand 0 must be a string (section name)
+
+!llvm.raw.sections = !{!0, !1, !2}
+!0 = !{!"__clangast", i32 8, i32 1, !"data"}
+!1 = !{!"__clangast", i32 8, !"data"}
+!2 = !{i32 0, i32 8, i32 1, !"data"}
>From 313b1f244a76231524142d25942d14dc12fb9ad7 Mon Sep 17 00:00:00 2001
From: Steffen Holst Larsen <sholstla at amd.com>
Date: Mon, 28 Sep 2026 10:59:27 -0500
Subject: [PATCH 2/2] Require power-of-two alignment
Signed-off-by: Steffen Holst Larsen <sholstla at amd.com>
---
llvm/docs/LangRef.md | 2 +-
llvm/lib/IR/Verifier.cpp | 9 ++++++++-
llvm/test/Verifier/raw-sections-align.ll | 19 +++++++++++++++++++
3 files changed, 28 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/Verifier/raw-sections-align.ll
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 81650a2a59dca..60d04a3698a78 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -9696,7 +9696,7 @@ following fields:
```
- **section_name**: The name of the output section.
-- **alignment**: The byte alignment of the section data.
+- **alignment**: The byte alignment of the section data. Must be a power-of-two.
- **flags**: A bitmask describing the properties of the section. Each target
maps this onto format-appropriate section flags. The supported bits are
listed below.
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index ec03ad1c3c2b4..61ff32bb301f3 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -1936,10 +1936,17 @@ void Verifier::visitModuleRawSections() {
"llvm.raw.sections entry operand 0 must be a string "
"(section name)",
N);
- Check(mdconst::dyn_extract_or_null<ConstantInt>(N->getOperand(1)),
+ auto *AlignCI = mdconst::dyn_extract_or_null<ConstantInt>(N->getOperand(1));
+ Check(AlignCI,
"llvm.raw.sections entry operand 1 must be an integer "
"(alignment)",
N);
+ if (!isPowerOf2_64(AlignCI->getZExtValue())) {
+ CheckFailed("llvm.raw.sections entry operand 1 must be a power of two "
+ "(alignment)",
+ N);
+ continue;
+ }
auto *FlagsCI = mdconst::dyn_extract_or_null<ConstantInt>(N->getOperand(2));
Check(FlagsCI,
"llvm.raw.sections entry operand 2 must be an integer "
diff --git a/llvm/test/Verifier/raw-sections-align.ll b/llvm/test/Verifier/raw-sections-align.ll
new file mode 100644
index 0000000000000..863656aa272ca
--- /dev/null
+++ b/llvm/test/Verifier/raw-sections-align.ll
@@ -0,0 +1,19 @@
+; RUN: not llvm-as < %s -o /dev/null 2>&1 | FileCheck %s
+
+; Zero alignment.
+; CHECK: llvm.raw.sections entry operand 1 must be a power of two (alignment)
+; CHECK-NEXT: !{!"__mydata", i32 0, i32 1, !"data"}
+
+; Non-power-of-two alignment.
+; CHECK: llvm.raw.sections entry operand 1 must be a power of two (alignment)
+; CHECK-NEXT: !{!"__mydata", i32 12, i32 1, !"data"}
+
+; CHECK-NOT: llvm.raw.sections
+
+!llvm.raw.sections = !{!0, !1, !2, !3}
+
+; Valid: alignment 1 and 8.
+!0 = !{!"__mydata", i32 1, i32 1, !"data"}
+!1 = !{!"__mydata", i32 8, i32 1, !"data"}
+!2 = !{!"__mydata", i32 0, i32 1, !"data"}
+!3 = !{!"__mydata", i32 12, i32 1, !"data"}
More information about the llvm-commits
mailing list