Skip to content

MC: Add ELF section and directive for specifying a section's preferred alignment. #150151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: users/pcc/spr/main.mc-add-elf-section-and-directive-for-specifying-a-sections-preferred-alignment
Choose a base branch
from

Conversation

pcc
Copy link
Contributor

@pcc pcc commented Jul 23, 2025

The notion of having a section's preferred function alignment be separate
from the minimum function alignment is useful in combination with CFI
jump table relaxation (#147424) as it allows functions to be well aligned
in the usual case with the associated performance benefits while giving
the linker permission to move functions into a lesser-aligned jump table
entry when that is the right tradeoff for the best performance. As noted
in #147424, jump table relaxation has been measured to reduce
the overhead of CFI in a large realistic internal Google benchmark
by between 0.2 and 0.5 percentage points, or 10-25%, depending on the
microarchitecture.

The new SHT_LLVM_MIN_ADDRALIGN section is used to specify the
minimum alignment of a section where that differs from its preferred
alignment. Its sh_link field identifies the section whose alignment is
being specified, its sh_addralign field specifies the linked section's
minimum alignment and the sh_addralign field of the linked section's
section header specifies its preferred alignment. This section has the
SHF_EXCLUDE flag so that it is stripped from the final executable or
shared library, and the SHF_LINK_ORDER flag so that the sh_link
field is updated by tools such as ld -r and objcopy. The contents
of the section must be empty.

The new asm directive:

.prefalign n

specifies that the preferred alignment of the current section is
determined by taking the maximum of n and the section's minimum
alignment, and causes an SHT_LLVM_MIN_ADDRALIGN section to be emitted
if necessary.

Created using spr 1.3.6-beta.1
@llvmbot llvmbot added llvm:mc Machine (object) code llvm:binary-utilities labels Jul 23, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 23, 2025

@llvm/pr-subscribers-llvm-binary-utilities

@llvm/pr-subscribers-mc

Author: Peter Collingbourne (pcc)

Changes

The new SHT_LLVM_MIN_ADDRALIGN section is used to specify the
minimum alignment of a section where that differs from its preferred
alignment. Its sh_link field identifies the section whose alignment is
being specified, its sh_addralign field specifies the linked section's
minimum alignment and the sh_addralign field of the linked section's
section header specifies its preferred alignment. This section has the
SHF_EXCLUDE flag so that it is stripped from the final executable or
shared library, and the SHF_LINK_ORDER flag so that the sh_link
field is updated by tools such as ld -r and objcopy. The contents
of the section must be empty.

The new asm directive:

.prefalign n

specifies that the preferred alignment of the current section is
determined by taking the maximum of n and the section's minimum
alignment, and causes an SHT_LLVM_MIN_ADDRALIGN section to be emitted
if necessary.


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

13 Files Affected:

  • (modified) llvm/docs/Extensions.rst (+22)
  • (modified) llvm/include/llvm/BinaryFormat/ELF.h (+1)
  • (modified) llvm/include/llvm/MC/MCObjectStreamer.h (+1)
  • (modified) llvm/include/llvm/MC/MCSection.h (+12)
  • (modified) llvm/include/llvm/MC/MCStreamer.h (+2)
  • (modified) llvm/lib/MC/ELFObjectWriter.cpp (+13-4)
  • (modified) llvm/lib/MC/MCAsmStreamer.cpp (+6)
  • (modified) llvm/lib/MC/MCObjectStreamer.cpp (+4)
  • (modified) llvm/lib/MC/MCParser/AsmParser.cpp (+20)
  • (modified) llvm/lib/MC/MCStreamer.cpp (+1)
  • (modified) llvm/lib/Object/ELF.cpp (+1)
  • (added) llvm/test/MC/ELF/prefalign-errors.s (+5)
  • (added) llvm/test/MC/ELF/prefalign.s (+47)
diff --git a/llvm/docs/Extensions.rst b/llvm/docs/Extensions.rst
index d8fb87b6998ad..e6b2db0745623 100644
--- a/llvm/docs/Extensions.rst
+++ b/llvm/docs/Extensions.rst
@@ -601,6 +601,28 @@ sees fit (generally the section that would provide the best locality).
 
 .. _CFI jump table: https://clang.llvm.org/docs/ControlFlowIntegrityDesign.html#forward-edge-cfi-for-indirect-function-calls
 
+``SHT_LLVM_MIN_ADDRALIGN`` Section (minimum section alignment)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+This section is used to specify the minimum alignment of a section
+where that differs from its preferred alignment. Its ``sh_link``
+field identifies the section whose alignment is being specified, its
+``sh_addralign`` field specifies the linked section's minimum alignment
+and the ``sh_addralign`` field of the linked section's section header
+specifies its preferred alignment. This section has the ``SHF_EXCLUDE``
+flag so that it is stripped from the final executable or shared library,
+and the ``SHF_LINK_ORDER`` flag so that the ``sh_link`` field is updated
+by tools such as ``ld -r`` and ``objcopy``. The contents of the section
+must be empty.
+
+.. code-block:: gas
+
+  .prefalign n
+
+Specifies that the preferred alignment of the current section is
+determined by taking the maximum of ``n`` and the section's minimum
+alignment, and causes an ``SHT_LLVM_MIN_ADDRALIGN`` section to be emitted
+if necessary.
+
 CodeView-Dependent
 ------------------
 
diff --git a/llvm/include/llvm/BinaryFormat/ELF.h b/llvm/include/llvm/BinaryFormat/ELF.h
index e4f82ad96a084..95600f39153d4 100644
--- a/llvm/include/llvm/BinaryFormat/ELF.h
+++ b/llvm/include/llvm/BinaryFormat/ELF.h
@@ -1160,6 +1160,7 @@ enum : unsigned {
   SHT_LLVM_LTO = 0x6fff4c0c,                // .llvm.lto for fat LTO.
   SHT_LLVM_JT_SIZES = 0x6fff4c0d,           // LLVM jump tables sizes.
   SHT_LLVM_CFI_JUMP_TABLE = 0x6fff4c0e,     // LLVM CFI jump table.
+  SHT_LLVM_MIN_ADDRALIGN = 0x6fff4c0f,      // Minimum alignment specification.
   // Android's experimental support for SHT_RELR sections.
   // https://android.googlesource.com/platform/bionic/+/b7feec74547f84559a1467aca02708ff61346d2a/libc/include/elf.h#512
   SHT_ANDROID_RELR = 0x6fffff00,   // Relocation entries; only offsets.
diff --git a/llvm/include/llvm/MC/MCObjectStreamer.h b/llvm/include/llvm/MC/MCObjectStreamer.h
index 319e131999d48..27de57f2ccd91 100644
--- a/llvm/include/llvm/MC/MCObjectStreamer.h
+++ b/llvm/include/llvm/MC/MCObjectStreamer.h
@@ -113,6 +113,7 @@ class MCObjectStreamer : public MCStreamer {
                             unsigned MaxBytesToEmit = 0) override;
   void emitCodeAlignment(Align ByteAlignment, const MCSubtargetInfo *STI,
                          unsigned MaxBytesToEmit = 0) override;
+  void emitPrefAlign(Align Alignment) override;
   void emitValueToOffset(const MCExpr *Offset, unsigned char Value,
                          SMLoc Loc) override;
   void emitDwarfLocDirective(unsigned FileNo, unsigned Line, unsigned Column,
diff --git a/llvm/include/llvm/MC/MCSection.h b/llvm/include/llvm/MC/MCSection.h
index 313071ec75033..61ae6dff4d146 100644
--- a/llvm/include/llvm/MC/MCSection.h
+++ b/llvm/include/llvm/MC/MCSection.h
@@ -82,6 +82,7 @@ class LLVM_ABI MCSection {
   MCSymbol *End = nullptr;
   /// The alignment requirement of this section.
   Align Alignment;
+  MaybeAlign PreferredAlignment;
   /// The section index in the assemblers section list.
   unsigned Ordinal = 0;
 
@@ -147,6 +148,17 @@ class LLVM_ABI MCSection {
       Alignment = MinAlignment;
   }
 
+  Align getPreferredAlignment() const {
+    if (!PreferredAlignment || Alignment > *PreferredAlignment)
+      return Alignment;
+    return *PreferredAlignment;
+  }
+
+  void ensurePreferredAlignment(Align PrefAlign) {
+    if (!PreferredAlignment || PrefAlign > *PreferredAlignment)
+      PreferredAlignment = PrefAlign;
+  }
+
   unsigned getOrdinal() const { return Ordinal; }
   void setOrdinal(unsigned Value) { Ordinal = Value; }
 
diff --git a/llvm/include/llvm/MC/MCStreamer.h b/llvm/include/llvm/MC/MCStreamer.h
index 4b91dbc794682..e7947657e1356 100644
--- a/llvm/include/llvm/MC/MCStreamer.h
+++ b/llvm/include/llvm/MC/MCStreamer.h
@@ -832,6 +832,8 @@ class LLVM_ABI MCStreamer {
   virtual void emitCodeAlignment(Align Alignment, const MCSubtargetInfo *STI,
                                  unsigned MaxBytesToEmit = 0);
 
+  virtual void emitPrefAlign(Align A);
+  
   /// Emit some number of copies of \p Value until the byte offset \p
   /// Offset is reached.
   ///
diff --git a/llvm/lib/MC/ELFObjectWriter.cpp b/llvm/lib/MC/ELFObjectWriter.cpp
index 9f52b3e3e85c0..7709cf56ffd32 100644
--- a/llvm/lib/MC/ELFObjectWriter.cpp
+++ b/llvm/lib/MC/ELFObjectWriter.cpp
@@ -926,10 +926,10 @@ void ELFWriter::writeSectionHeader(uint32_t GroupSymbolIndex, uint64_t Offset,
       sh_link = Sym->getSection().getOrdinal();
   }
 
-  writeSectionHeaderEntry(StrTabBuilder.getOffset(Section.getName()),
-                          Section.getType(), Section.getFlags(), 0, Offset,
-                          Size, sh_link, sh_info, Section.getAlign(),
-                          Section.getEntrySize());
+  writeSectionHeaderEntry(
+      StrTabBuilder.getOffset(Section.getName()), Section.getType(),
+      Section.getFlags(), 0, Offset, Size, sh_link, sh_info,
+      Section.getPreferredAlignment(), Section.getEntrySize());
 }
 
 void ELFWriter::writeSectionHeaders() {
@@ -1062,6 +1062,15 @@ uint64_t ELFWriter::writeObject() {
       Relocations.push_back(RelSection);
     }
 
+    if (Sec.getPreferredAlignment() != Sec.getAlign()) {
+      MCSectionELF *MinAlign = Ctx.getELFSection(
+          ".llvm.minalign", ELF::SHT_LLVM_MIN_ADDRALIGN,
+          ELF::SHF_EXCLUDE | ELF::SHF_LINK_ORDER, 0, "", false, 0,
+          cast<MCSymbolELF>(Section.getBeginSymbol()));
+      MinAlign->setOrdinal(addToSectionTable(MinAlign));
+      MinAlign->setAlignment(Sec.getAlign());
+    }
+
     if (GroupIdxEntry) {
       auto &Members = Groups[GroupMap[*GroupIdxEntry]];
       Members.second.push_back(Section.getOrdinal());
diff --git a/llvm/lib/MC/MCAsmStreamer.cpp b/llvm/lib/MC/MCAsmStreamer.cpp
index 67c53e01a6111..4d53607400a27 100644
--- a/llvm/lib/MC/MCAsmStreamer.cpp
+++ b/llvm/lib/MC/MCAsmStreamer.cpp
@@ -275,6 +275,7 @@ class MCAsmStreamer final : public MCStreamer {
 
   void emitCodeAlignment(Align Alignment, const MCSubtargetInfo *STI,
                          unsigned MaxBytesToEmit = 0) override;
+  void emitPrefAlign(Align Alignment) override;
 
   void emitValueToOffset(const MCExpr *Offset,
                          unsigned char Value,
@@ -1540,6 +1541,11 @@ void MCAsmStreamer::emitCodeAlignment(Align Alignment,
     emitAlignmentDirective(Alignment.value(), std::nullopt, 1, MaxBytesToEmit);
 }
 
+void MCAsmStreamer::emitPrefAlign(Align Alignment) {
+  OS << ".prefalign " << Alignment.value();
+  EmitEOL();
+}
+
 void MCAsmStreamer::emitValueToOffset(const MCExpr *Offset,
                                       unsigned char Value,
                                       SMLoc Loc) {
diff --git a/llvm/lib/MC/MCObjectStreamer.cpp b/llvm/lib/MC/MCObjectStreamer.cpp
index d5b8f22463894..e860f6c61c5f7 100644
--- a/llvm/lib/MC/MCObjectStreamer.cpp
+++ b/llvm/lib/MC/MCObjectStreamer.cpp
@@ -567,6 +567,10 @@ void MCObjectStreamer::emitCodeAlignment(Align Alignment,
   }
 }
 
+void MCObjectStreamer::emitPrefAlign(Align Alignment) {
+  getCurrentSectionOnly()->ensurePreferredAlignment(Alignment);
+}
+
 void MCObjectStreamer::emitValueToOffset(const MCExpr *Offset,
                                          unsigned char Value,
                                          SMLoc Loc) {
diff --git a/llvm/lib/MC/MCParser/AsmParser.cpp b/llvm/lib/MC/MCParser/AsmParser.cpp
index 77bf84364c5a3..2e85c08852c15 100644
--- a/llvm/lib/MC/MCParser/AsmParser.cpp
+++ b/llvm/lib/MC/MCParser/AsmParser.cpp
@@ -418,6 +418,7 @@ class AsmParser : public MCAsmParser {
     DK_P2ALIGN,
     DK_P2ALIGNW,
     DK_P2ALIGNL,
+    DK_PREFALIGN,
     DK_ORG,
     DK_FILL,
     DK_ENDR,
@@ -565,6 +566,7 @@ class AsmParser : public MCAsmParser {
   bool parseDirectiveOrg(); // ".org"
   // ".align{,32}", ".p2align{,w,l}"
   bool parseDirectiveAlign(bool IsPow2, uint8_t ValueSize);
+  bool parseDirectivePrefAlign();
 
   // ".file", ".line", ".loc", ".loc_label", ".stabs"
   bool parseDirectiveFile(SMLoc DirectiveLoc);
@@ -2000,6 +2002,8 @@ bool AsmParser::parseStatement(ParseStatementInfo &Info,
       return parseDirectiveAlign(/*IsPow2=*/true, /*ExprSize=*/2);
     case DK_P2ALIGNL:
       return parseDirectiveAlign(/*IsPow2=*/true, /*ExprSize=*/4);
+    case DK_PREFALIGN:
+      return parseDirectivePrefAlign();
     case DK_ORG:
       return parseDirectiveOrg();
     case DK_FILL:
@@ -3426,6 +3430,21 @@ bool AsmParser::parseDirectiveAlign(bool IsPow2, uint8_t ValueSize) {
   return ReturnVal;
 }
 
+bool AsmParser::parseDirectivePrefAlign() {
+  SMLoc AlignmentLoc = getLexer().getLoc();
+  int64_t Alignment;
+  if (checkForValidSection() || parseAbsoluteExpression(Alignment))
+    return true;
+  if (parseEOL())
+    return true;
+
+  if (!isPowerOf2_64(Alignment))
+    return Error(AlignmentLoc, "alignment must be a power of 2");
+  getStreamer().emitPrefAlign(Align(Alignment));
+
+  return false;
+}
+
 /// parseDirectiveFile
 /// ::= .file filename
 /// ::= .file number [directory] filename [md5 checksum] [source source-text]
@@ -5377,6 +5396,7 @@ void AsmParser::initializeDirectiveKindMap() {
   DirectiveKindMap[".p2align"] = DK_P2ALIGN;
   DirectiveKindMap[".p2alignw"] = DK_P2ALIGNW;
   DirectiveKindMap[".p2alignl"] = DK_P2ALIGNL;
+  DirectiveKindMap[".prefalign"] = DK_PREFALIGN;
   DirectiveKindMap[".org"] = DK_ORG;
   DirectiveKindMap[".fill"] = DK_FILL;
   DirectiveKindMap[".zero"] = DK_ZERO;
diff --git a/llvm/lib/MC/MCStreamer.cpp b/llvm/lib/MC/MCStreamer.cpp
index c3ecf8fc717f5..eeca414da580d 100644
--- a/llvm/lib/MC/MCStreamer.cpp
+++ b/llvm/lib/MC/MCStreamer.cpp
@@ -1329,6 +1329,7 @@ void MCStreamer::emitFill(const MCExpr &NumBytes, uint64_t Value, SMLoc Loc) {}
 void MCStreamer::emitFill(const MCExpr &NumValues, int64_t Size, int64_t Expr,
                           SMLoc Loc) {}
 void MCStreamer::emitValueToAlignment(Align, int64_t, uint8_t, unsigned) {}
+void MCStreamer::emitPrefAlign(Align A) {}
 void MCStreamer::emitCodeAlignment(Align Alignment, const MCSubtargetInfo *STI,
                                    unsigned MaxBytesToEmit) {}
 void MCStreamer::emitValueToOffset(const MCExpr *Offset, unsigned char Value,
diff --git a/llvm/lib/Object/ELF.cpp b/llvm/lib/Object/ELF.cpp
index 788c6020a7f99..ab23a0b1f25b9 100644
--- a/llvm/lib/Object/ELF.cpp
+++ b/llvm/lib/Object/ELF.cpp
@@ -322,6 +322,7 @@ StringRef llvm::object::getELFSectionTypeName(uint32_t Machine, unsigned Type) {
     STRINGIFY_ENUM_CASE(ELF, SHT_LLVM_LTO);
     STRINGIFY_ENUM_CASE(ELF, SHT_LLVM_JT_SIZES)
     STRINGIFY_ENUM_CASE(ELF, SHT_LLVM_CFI_JUMP_TABLE)
+    STRINGIFY_ENUM_CASE(ELF, SHT_LLVM_MIN_ADDRALIGN)
     STRINGIFY_ENUM_CASE(ELF, SHT_GNU_SFRAME);
     STRINGIFY_ENUM_CASE(ELF, SHT_GNU_ATTRIBUTES);
     STRINGIFY_ENUM_CASE(ELF, SHT_GNU_HASH);
diff --git a/llvm/test/MC/ELF/prefalign-errors.s b/llvm/test/MC/ELF/prefalign-errors.s
new file mode 100644
index 0000000000000..363638f9bcb1e
--- /dev/null
+++ b/llvm/test/MC/ELF/prefalign-errors.s
@@ -0,0 +1,5 @@
+// RUN: not llvm-mc -filetype=asm -triple x86_64-pc-linux-gnu %s -o - 2>&1 | FileCheck %s
+
+.section .text.f1,"ax",@progbits
+// CHECK: error: alignment must be a power of 2
+.prefalign 3
diff --git a/llvm/test/MC/ELF/prefalign.s b/llvm/test/MC/ELF/prefalign.s
new file mode 100644
index 0000000000000..f3537029b23c1
--- /dev/null
+++ b/llvm/test/MC/ELF/prefalign.s
@@ -0,0 +1,47 @@
+// RUN: llvm-mc -filetype=asm -triple x86_64-pc-linux-gnu %s -o - | FileCheck --check-prefix=ASM %s
+// RUN: llvm-mc -filetype=obj -triple x86_64-pc-linux-gnu %s -o - | llvm-readelf -SW - | FileCheck --check-prefix=OBJ %s
+
+// Minimum alignment = preferred alignment, no SHT_LLVM_MIN_ADDRALIGN needed.
+// ASM: .section .text.f1
+// ASM: .p2align 2
+// ASM: .prefalign 4 
+// OBJ: .text.f1
+// OBJ-NOT: .llvm.minalign
+.section .text.f1,"ax",@progbits
+.p2align 2
+.prefalign 4
+
+// Minimum alignment < preferred alignment, SHT_LLVM_MIN_ADDRALIGN emitted.
+// ASM: .section .text.f2
+// ASM: .p2align 2
+// ASM: .prefalign 8 
+// OBJ: [ 4] .text.f2          PROGBITS           0000000000000000 000040 000000 00  AX  0   0  8
+// OBJ: [ 5] .llvm.minalign    LLVM_MIN_ADDRALIGN 0000000000000000 000000 000000 00  LE  4   0  4
+.section .text.f2,"ax",@progbits
+.p2align 2
+.prefalign 8
+
+// Minimum alignment > preferred alignment, preferred alignment rounded up to
+// minimum alignment. No SHT_LLVM_MIN_ADDRALIGN emitted.
+// ASM: .section .text.f3
+// ASM: .p2align 3
+// ASM: .prefalign 4 
+// OBJ: .text.f3
+// OBJ-NOT: .llvm.minalign
+.section .text.f3,"ax",@progbits
+.p2align 3
+.prefalign 4
+
+// Maximum of all .prefalign directives written to object file.
+// ASM: .section .text.f4
+// ASM: .p2align 2
+// ASM: .prefalign 8
+// ASM: .prefalign 16
+// ASM: .prefalign 8
+// OBJ: [ 7] .text.f4          PROGBITS           0000000000000000 000040 000000 00  AX  0   0 16
+// OBJ: [ 8] .llvm.minalign    LLVM_MIN_ADDRALIGN 0000000000000000 000000 000000 00  LE  7   0  4
+.section .text.f4,"ax",@progbits
+.p2align 2
+.prefalign 8
+.prefalign 16
+.prefalign 8

@pcc pcc requested review from MaskRay and efriedma-quic July 23, 2025 01:31
Copy link

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions cpp,h -- llvm/include/llvm/BinaryFormat/ELF.h llvm/include/llvm/MC/MCObjectStreamer.h llvm/include/llvm/MC/MCSection.h llvm/include/llvm/MC/MCStreamer.h llvm/lib/MC/ELFObjectWriter.cpp llvm/lib/MC/MCAsmStreamer.cpp llvm/lib/MC/MCObjectStreamer.cpp llvm/lib/MC/MCParser/AsmParser.cpp llvm/lib/MC/MCStreamer.cpp llvm/lib/Object/ELF.cpp
View the diff from clang-format here.
diff --git a/llvm/include/llvm/MC/MCStreamer.h b/llvm/include/llvm/MC/MCStreamer.h
index e7947657e..1f9ee1c5f 100644
--- a/llvm/include/llvm/MC/MCStreamer.h
+++ b/llvm/include/llvm/MC/MCStreamer.h
@@ -833,7 +833,7 @@ public:
                                  unsigned MaxBytesToEmit = 0);
 
   virtual void emitPrefAlign(Align A);
-  
+
   /// Emit some number of copies of \p Value until the byte offset \p
   /// Offset is reached.
   ///

@MaskRay
Copy link
Member

MaskRay commented Jul 27, 2025

There are some discussions on a codegen patch #149444 that I did not follow. Could you summarize the motivation in this patch's description?

@pcc
Copy link
Contributor Author

pcc commented Jul 27, 2025

There are some discussions on a codegen patch #149444 that I did not follow. Could you summarize the motivation in this patch's description?

Sure. The motivation is the same as for #149448 which is an alternative to this approach. I added it to the first paragraph of this PR's description.

@pcc
Copy link
Contributor Author

pcc commented Aug 18, 2025

Ping, any thoughts on this approach?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants