[llvm] [clang] [clang] Support per-function [[clang::code_align(N)]] attribute. (PR #80765)

Anton Bikineev via cfe-commits cfe-commits at lists.llvm.org
Mon Feb 5 15:51:21 PST 2024


https://github.com/AntonBikineev created https://github.com/llvm/llvm-project/pull/80765

The existing attribute works only for loop headers. This can unfortunately be quite limiting, especially as a protection against the "Jump Conditional Code Erratum" [0] (for example, if there is an unaligned check in a hot loop). The command line option -malign-branch-boundary can help with that, but it's too coarse-grained and can significantly increase the binary size.

This change introduces a per-function [[clang::code_align(N)]] attribute that aligns all the basic blocks of a function by the given N.

[0] https://www.intel.com/content/dam/support/us/en/documents/processors/mitigations-jump-conditional-code-erratum.pdf

>From ff8cf4e87473dec2e3f55114cb92ae5a63d9488c Mon Sep 17 00:00:00 2001
From: Anton Bikineev <bikineev at chromium.org>
Date: Mon, 5 Feb 2024 12:24:17 +0100
Subject: [PATCH] [clang] Support per-function [[clang::code_align(N)]]
 attribute.

The existing attribute works only for loop headers. This can
unfortunately be quite limiting, especially as a protection against the
"Jump Conditional Code Erratum" [0] (for example, if there is an
unaligned check in a hot loop). The command line option
-malign-branch-boundary can help with that, but it's too coarse-grained
and can significantly increase the binary size.

This change introduces a per-function [[clang::code_align(N)]] attribute
that aligns all the basic blocks of a function by the given N.

[0] https://www.intel.com/content/dam/support/us/en/documents/processors/mitigations-jump-conditional-code-erratum.pdf
---
 clang/include/clang/Basic/Attr.td             |  7 ++--
 clang/include/clang/Basic/AttrDocs.td         | 16 ++++---
 clang/lib/CodeGen/CodeGenModule.cpp           |  6 +++
 clang/lib/Sema/SemaDeclAttr.cpp               | 10 +++++
 clang/test/CodeGen/code_align_function.c      | 42 +++++++++++++++++++
 clang/test/Sema/code_align.c                  | 14 ++++++-
 llvm/lib/CodeGen/MachineBlockPlacement.cpp    | 27 +++++++++---
 .../CodeGen/X86/code-align-basic-blocks.ll    | 29 +++++++++++++
 8 files changed, 135 insertions(+), 16 deletions(-)
 create mode 100644 clang/test/CodeGen/code_align_function.c
 create mode 100644 llvm/test/CodeGen/X86/code-align-basic-blocks.ll

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index b2d5309e142c1..abce685e9f7a6 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4435,10 +4435,11 @@ def PreferredType: InheritableAttr {
   let Documentation = [PreferredTypeDocumentation];
 }
 
-def CodeAlign: StmtAttr {
+def CodeAlign : InheritableAttr {
   let Spellings = [Clang<"code_align">];
-  let Subjects = SubjectList<[ForStmt, CXXForRangeStmt, WhileStmt, DoStmt],
-                              ErrorDiag, "'for', 'while', and 'do' statements">;
+  let Subjects =
+      SubjectList<[ForStmt, CXXForRangeStmt, WhileStmt, DoStmt, Function],
+                  ErrorDiag, "'for', 'while', 'do' statements and functions">;
   let Args = [ExprArgument<"Alignment">];
   let Documentation = [CodeAlignAttrDocs];
   let AdditionalMembers = [{
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 041786f37fb8a..57b63469f1573 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -7704,11 +7704,12 @@ def CodeAlignAttrDocs : Documentation {
   let Category = DocCatVariable;
   let Heading = "clang::code_align";
   let Content = [{
-The ``clang::code_align(N)`` attribute applies to a loop and specifies the byte
-alignment for a loop. The attribute accepts a positive integer constant
-initialization expression indicating the number of bytes for the minimum
-alignment boundary. Its value must be a power of 2, between 1 and 4096
-(inclusive).
+The ``clang::code_align(N)`` attribute applies to a loop or a function. When
+applied to a loop it specifies the byte alignment for the loop header. When
+applied to a function it aligns all the basic blocks of the function. The
+attribute accepts a positive integer constant initialization expression
+indicating the number of bytes for the minimum alignment boundary. Its value
+must be a power of 2, between 1 and 4096 (inclusive).
 
 .. code-block:: c++
 
@@ -7738,6 +7739,11 @@ alignment boundary. Its value must be a power of 2, between 1 and 4096
     [[clang::code_align(A)]] for(;;) { }
   }
 
+  [[clang:code_align(16)]] int foo(bool b) {
+    if (b) return 2;
+    return 3;
+  }
+
   }];
 }
 
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 36b63d78b06f8..87a822e91fff6 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -2515,6 +2515,12 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D,
       F->setAlignment(std::max(llvm::Align(2), F->getAlign().valueOrOne()));
   }
 
+  if (const auto *CodeAlign = D->getAttr<CodeAlignAttr>()) {
+    const auto *CE = cast<ConstantExpr>(CodeAlign->getAlignment());
+    std::string ArgValStr = llvm::toString(CE->getResultAsAPSInt(), 10);
+    F->addFnAttr("align-basic-blocks", ArgValStr);
+  }
+
   // In the cross-dso CFI mode with canonical jump tables, we want !type
   // attributes on definitions only.
   if (CodeGenOpts.SanitizeCfiCrossDso &&
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index d785714c4d811..40412801b632a 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -9036,6 +9036,12 @@ static void handleArmNewAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
                  ArmNewAttr(S.Context, AL, NewState.data(), NewState.size()));
 }
 
+static void handleCodeAlignAttr(Sema &S, Decl *D, const ParsedAttr &A) {
+  Expr *E = A.getArgAsExpr(0);
+  if (Attr *CodeAlignAttr = S.BuildCodeAlignAttr(A, E))
+    D->addAttr(CodeAlignAttr);
+}
+
 /// ProcessDeclAttribute - Apply the specific attribute to the specified decl if
 /// the attribute applies to decls.  If the attribute is a type attribute, just
 /// silently ignore it if a GNU attribute.
@@ -9855,6 +9861,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_UsingIfExists:
     handleSimpleAttribute<UsingIfExistsAttr>(S, D, AL);
     break;
+
+  case ParsedAttr::AT_CodeAlign:
+    handleCodeAlignAttr(S, D, AL);
+    break;
   }
 }
 
diff --git a/clang/test/CodeGen/code_align_function.c b/clang/test/CodeGen/code_align_function.c
new file mode 100644
index 0000000000000..8289e1e307cd0
--- /dev/null
+++ b/clang/test/CodeGen/code_align_function.c
@@ -0,0 +1,42 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -x c %s %s -o - | FileCheck -check-prefix=CHECK-C %s
+// RUN: %clang_cc1 -fsyntax-only -emit-llvm -x c++ -std=c++11 %s -o - | FileCheck %s --check-prefixes CHECK-CPP
+
+// CHECK-C: define dso_local i32 @code_align_function(i32 noundef %a) #[[A0:[0-9]+]]
+// CHECK-C: define dso_local i32 @code_align_function_with_aligned_loop(i32 noundef %a) #[[A1:[0-9]+]]
+
+// CHECK-C: attributes #[[A0]] = {{.*}} "align-basic-blocks"="32"
+[[clang::code_align(32)]] int code_align_function(int a) {
+  if (a) {
+    return 2;
+  }
+  return 3;
+}
+
+// CHECK-C: attributes #[[A1]] = {{.*}} "align-basic-blocks"="64"
+[[clang::code_align(64)]] int code_align_function_with_aligned_loop(int a) {
+  if (a) {
+    return 2;
+  }
+  int c = 0;
+  // CHECK-C: !{!"llvm.loop.align", i32 128}
+  [[clang::code_align(128)]] for (int i = 0; i < a; ++i) {
+    c += i;
+  }
+  return c;
+}
+
+#if __cplusplus >= 201103L
+struct S {
+  // CHECK-CPP: @_ZN1S19code_align_functionILi1EEEii({{.*}}) #[[A2:[0-9]+]]
+  // CHECK-CPP: attributes #[[A2]] = {{.*}} "align-basic-blocks"="16"
+  template <int A>
+  [[clang::code_align(16)]] int code_align_function(int a) {
+    if (a) {
+      return 2;
+    }
+    return 3;
+  }
+};
+
+template int S::code_align_function<1>(int);
+#endif
diff --git a/clang/test/Sema/code_align.c b/clang/test/Sema/code_align.c
index d494d5ea1558f..c56e716436c86 100644
--- a/clang/test/Sema/code_align.c
+++ b/clang/test/Sema/code_align.c
@@ -9,14 +9,14 @@ void foo() {
   for (i = 0; i < 10; ++i) {  // this is OK
     a[i] = b[i] = 0;
   }
-  // expected-error at +1{{'code_align' attribute only applies to 'for', 'while', and 'do' statements}}
+  // expected-error at +1{{'code_align' attribute only applies to 'for', 'while', 'do' statements and functions}}
   [[clang::code_align(4)]]
   i = 7;
   for (i = 0; i < 10; ++i) {
     a[i] = b[i] = 0;
   }
 
-  // expected-error at +1{{'code_align' attribute cannot be applied to a declaration}}
+  // expected-error at +1{{'code_align' attribute only applies to 'for', 'while', 'do' statements and functions}}
   [[clang::code_align(12)]] int n[10];
 }
 
@@ -121,6 +121,11 @@ void check_code_align_expression() {
 #endif
 }
 
+[[clang::code_align(32)]] int function_attribute();
+
+// expected-error at +1{{'code_align' attribute requires an integer argument which is a constant power of two}}
+[[clang::code_align(31)]] int function_attribute_misaligned();
+
 #if __cplusplus >= 201103L
 template <int A, int B, int C, int D, int E>
 void code_align_dependent() {
@@ -161,6 +166,11 @@ void bar4() {
   for(int I=0; I<128; ++I) { bar(I); }
 }
 
+struct S {
+  template <int A>
+  [[clang::code_align(32)]] int foo();
+};
+
 int main() {
   code_align_dependent<8, 23, 32, -10, 64>(); // #neg-instantiation
   bar3<4>();  // #temp-instantiation
diff --git a/llvm/lib/CodeGen/MachineBlockPlacement.cpp b/llvm/lib/CodeGen/MachineBlockPlacement.cpp
index ef34e920aed50..ba4f161ef3250 100644
--- a/llvm/lib/CodeGen/MachineBlockPlacement.cpp
+++ b/llvm/lib/CodeGen/MachineBlockPlacement.cpp
@@ -2940,8 +2940,12 @@ void MachineBlockPlacement::alignBlocks() {
       }
     }
 
-    // Use max of the TLIAlign and MDAlign
-    const Align LoopAlign = std::max(TLIAlign, Align(MDAlign));
+    unsigned FunctionMBBAlign =
+        F->getFunction().getFnAttributeAsParsedInteger("align-basic-blocks", 1);
+
+    // Use max of the TLIAlign, MDAlign or the function-level alignment.
+    const Align LoopAlign =
+        std::max(std::max(TLIAlign, Align(MDAlign)), Align(FunctionMBBAlign));
     if (LoopAlign == 1)
       continue; // Don't care about loop alignment.
 
@@ -3475,28 +3479,39 @@ bool MachineBlockPlacement::runOnMachineFunction(MachineFunction &MF) {
   bool HasMaxBytesOverride =
       MaxBytesForAlignmentOverride.getNumOccurrences() > 0;
 
+  unsigned long long MBBAlignment =
+      MF.getFunction().getFnAttributeAsParsedInteger("align-basic-blocks", 1);
+
+  // Respect BB alignment that could already be set by llvm.loop.align in
+  // alignBlocks() above.
   if (AlignAllBlock)
     // Align all of the blocks in the function to a specific alignment.
     for (MachineBasicBlock &MBB : MF) {
+      unsigned MaxAlignment = std::max(1ULL << AlignAllBlock, MBBAlignment);
       if (HasMaxBytesOverride)
-        MBB.setAlignment(Align(1ULL << AlignAllBlock),
+        MBB.setAlignment(std::max(Align(MaxAlignment), MBB.getAlignment()),
                          MaxBytesForAlignmentOverride);
       else
-        MBB.setAlignment(Align(1ULL << AlignAllBlock));
+        MBB.setAlignment(std::max(Align(MaxAlignment), MBB.getAlignment()));
     }
   else if (AlignAllNonFallThruBlocks) {
     // Align all of the blocks that have no fall-through predecessors to a
     // specific alignment.
     for (auto MBI = std::next(MF.begin()), MBE = MF.end(); MBI != MBE; ++MBI) {
       auto LayoutPred = std::prev(MBI);
+      unsigned MaxAlignment = std::max(1ULL << AlignAllNonFallThruBlocks, MBBAlignment);
       if (!LayoutPred->isSuccessor(&*MBI)) {
         if (HasMaxBytesOverride)
-          MBI->setAlignment(Align(1ULL << AlignAllNonFallThruBlocks),
+          MBI->setAlignment(std::max(Align(MaxAlignment), MBI->getAlignment()),
                             MaxBytesForAlignmentOverride);
         else
-          MBI->setAlignment(Align(1ULL << AlignAllNonFallThruBlocks));
+          MBI->setAlignment(std::max(Align(MaxAlignment), MBI->getAlignment()));
       }
     }
+  } else if (MBBAlignment != 1) {
+    for (MachineBasicBlock &MBB : MF) {
+      MBB.setAlignment(std::max(Align(MBBAlignment), MBB.getAlignment()));
+    }
   }
   if (ViewBlockLayoutWithBFI != GVDT_None &&
       (ViewBlockFreqFuncName.empty() ||
diff --git a/llvm/test/CodeGen/X86/code-align-basic-blocks.ll b/llvm/test/CodeGen/X86/code-align-basic-blocks.ll
new file mode 100644
index 0000000000000..7cc1a046aef5c
--- /dev/null
+++ b/llvm/test/CodeGen/X86/code-align-basic-blocks.ll
@@ -0,0 +1,29 @@
+; RUN: llc < %s -mtriple=x86_64-pc-linux-gnu | FileCheck %s -check-prefixes=CHECK
+
+declare void @bar();
+
+; CHECK-LABEL: foo:
+; CHECK-LABEL: # %bb.0:
+; CHECK: .p2align        5, 0x90
+; CHECK-LABEL: # %bb.1:
+; CHECK: .p2align        10, 0x90
+; CHECK-LABEL: .LBB0_2:
+; CHECK: .p2align        5, 0x90
+; CHECK-LABEL: # %bb.3:
+; CHECK: .p2align        5, 0x90
+
+define i32 @foo(i1 %1) "align-basic-blocks"="32" {
+  br i1 %1, label %7, label %3
+3:
+  %4 = phi i32 [ %5, %3 ], [ 0, %2 ]
+  call void @bar()
+  %5 = add nuw nsw i32 %4, 1
+  %6 = icmp eq i32 %5, 90
+  br i1 %6, label %7, label %3, !llvm.loop !0
+7:
+  %8 = phi i32 [ 2, %2 ], [ 3, %3 ]
+  ret i32 %8
+}
+
+!0 = distinct !{!0, !1}
+!1 = !{!"llvm.loop.align", i32 1024}



More information about the cfe-commits mailing list