[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