[clang] [llvm] [clang] Add clang::nooutline Attribute (PR #163493)

Sam Elliott via cfe-commits cfe-commits at lists.llvm.org
Tue Oct 14 21:51:35 PDT 2025


https://github.com/lenary created https://github.com/llvm/llvm-project/pull/163493

This PR:
- Adds a `[[clang::nooutline]]` function attribute for C and C++. There
  is no equivalent GNU syntax for this attribute, so no `__attribute__`
  syntax.
- Uses the presence of `[[clang::nooutline]]` to add the `nooutline`
  attribute to IR function definitions.
- Turns the `"nooutline"` attribute into an enum attribute (without
  quotes), and adds an auto-upgrader for bitcode to make that same
  change to existing IR.
- Adds test for the above.

The attribute is capable of disabling both the Machine Outliner (enabled
at Oz for some targets), and the IR Outliner (disabled by default).

>From 19afc83278b346629053b52d4655049938332342 Mon Sep 17 00:00:00 2001
From: Sam Elliott <aelliott at qti.qualcomm.com>
Date: Tue, 14 Oct 2025 21:11:24 -0700
Subject: [PATCH] [clang] Add clang::nooutline Attribute

This PR:
- Adds a `[[clang::nooutline]]` function attribute for C and C++. There
  is no equivalent GNU syntax for this attribute, so no `__attribute__`
  syntax.
- Uses the presence of `[[clang::nooutline]]` to add the `nooutline`
  attribute to IR function definitions.
- Turns the `"nooutline"` attribute into an enum attribute (without
  quotes), and adds an auto-upgrader for bitcode to make that same
  change to existing IR.
- Adds test for the above.

The attribute is capable of disabling both the Machine Outliner (enabled
at Oz for some targets), and the IR Outliner (disabled by default).
---
 clang/include/clang/Basic/Attr.td                |  7 +++++++
 clang/lib/CodeGen/CodeGenModule.cpp              |  3 +++
 clang/test/CodeGen/attr-nooutline.c              | 16 ++++++++++++++++
 clang/test/Sema/attr-nooutline.c                 |  8 ++++++++
 clang/test/Sema/attr-nooutline.cpp               |  7 +++++++
 llvm/docs/LangRef.rst                            |  2 +-
 llvm/include/llvm/Bitcode/LLVMBitCodes.h         |  1 +
 llvm/include/llvm/IR/Attributes.td               |  3 +++
 llvm/lib/Bitcode/Reader/BitcodeReader.cpp        |  2 ++
 llvm/lib/Bitcode/Writer/BitcodeWriter.cpp        |  2 ++
 llvm/lib/CodeGen/MachineOutliner.cpp             |  2 +-
 llvm/lib/IR/AutoUpgrade.cpp                      |  6 ++++++
 llvm/lib/Transforms/IPO/IROutliner.cpp           |  2 +-
 llvm/test/Bitcode/upgrade-nooutline.ll           |  8 ++++++++
 .../machine-outliner-mapper-debug-output.mir     |  2 +-
 .../Transforms/IROutliner/nooutline-attribute.ll |  4 ++--
 16 files changed, 69 insertions(+), 6 deletions(-)
 create mode 100644 clang/test/CodeGen/attr-nooutline.c
 create mode 100644 clang/test/Sema/attr-nooutline.c
 create mode 100644 clang/test/Sema/attr-nooutline.cpp
 create mode 100644 llvm/test/Bitcode/upgrade-nooutline.ll

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 22e60aa9fe312..b8a61ba4cbac9 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2355,6 +2355,13 @@ def NoInline : DeclOrStmtAttr {
   let SimpleHandler = 1;
 }
 
+def NoOutline : DeclOrStmtAttr {
+  let Spellings = [CXX11<"clang", "nooutline">, C23<"clang", "nooutline">];
+  let Subjects = SubjectList<[Function], ErrorDiag>;
+  let Documentation = [Undocumented];
+  let SimpleHandler = 1;
+}
+
 def NoMips16 : InheritableAttr, TargetSpecificAttr<TargetMips32> {
   let Spellings = [GCC<"nomips16">];
   let Subjects = SubjectList<[Function], ErrorDiag>;
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 8d019d4b2da25..ab267236ed579 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -2820,6 +2820,9 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D,
       B.addAttribute(llvm::Attribute::MinSize);
   }
 
+  if (D->hasAttr<NoOutlineAttr>())
+    B.addAttribute(llvm::Attribute::NoOutline);
+
   F->addFnAttrs(B);
 
   unsigned alignment = D->getMaxAlignment() / Context.getCharWidth();
diff --git a/clang/test/CodeGen/attr-nooutline.c b/clang/test/CodeGen/attr-nooutline.c
new file mode 100644
index 0000000000000..b9f175da24cb5
--- /dev/null
+++ b/clang/test/CodeGen/attr-nooutline.c
@@ -0,0 +1,16 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --check-attributes --version 6
+// RUN: %clang_cc1 -emit-llvm %s -triple x86_64-unknown-linux-gnu -disable-O0-optnone -o - | FileCheck %s
+
+
+// CHECK: Function Attrs: noinline nooutline nounwind
+// CHECK-LABEL: define dso_local i32 @t1(
+// CHECK-SAME: i32 noundef [[X:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[X_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT:    store i32 [[X]], ptr [[X_ADDR]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NEXT:    ret i32 [[TMP0]]
+//
+[[clang::nooutline]] int t1(int x) {
+  return x;
+}
diff --git a/clang/test/Sema/attr-nooutline.c b/clang/test/Sema/attr-nooutline.c
new file mode 100644
index 0000000000000..05ff644cfdb73
--- /dev/null
+++ b/clang/test/Sema/attr-nooutline.c
@@ -0,0 +1,8 @@
+// RUN: %clang_cc1 %s -verify -fsyntax-only
+
+[[clang::nooutline]] int a; // expected-error {{'clang::nooutline' attribute only applies to functions}}
+
+[[clang::nooutline]] void t1(void);
+
+[[clang::nooutline(2)]] void t2(void); // expected-error {{'clang::nooutline' attribute takes no arguments}}
+
diff --git a/clang/test/Sema/attr-nooutline.cpp b/clang/test/Sema/attr-nooutline.cpp
new file mode 100644
index 0000000000000..b6c9b3995081a
--- /dev/null
+++ b/clang/test/Sema/attr-nooutline.cpp
@@ -0,0 +1,7 @@
+// RUN: %clang_cc1 -verify -fsyntax-only %s -Wno-c++17-extensions
+
+[[clang::nooutline]] int a; // expected-error {{'clang::nooutline' attribute only applies to functions}}
+
+[[clang::nooutline]] void t1(void);
+
+[[clang::nooutline(2)]] void t2(void); // expected-error {{'clang::nooutline' attribute takes no arguments}}
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 4884e2dcbbe00..73887d1039488 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -2738,7 +2738,7 @@ For example:
     to signify an unbounded maximum. The syntax `vscale_range(<val>)` can be
     used to set both `min` and `max` to the same value. Functions that don't
     include this attribute make no assumptions about the value of `vscale`.
-``"nooutline"``
+``nooutline``
     This attribute indicates that outlining passes should not modify the
     function.
 
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index 464f475098ec5..95596273aad69 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -801,6 +801,7 @@ enum AttributeKindCodes {
   ATTR_KIND_CAPTURES = 102,
   ATTR_KIND_DEAD_ON_RETURN = 103,
   ATTR_KIND_SANITIZE_ALLOC_TOKEN = 104,
+  ATTR_KIND_NOOUTLINE = 105,
 };
 
 enum ComdatSelectionKindCodes {
diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index 8e7d9dcebfe2a..46a77ec121039 100644
--- a/llvm/include/llvm/IR/Attributes.td
+++ b/llvm/include/llvm/IR/Attributes.td
@@ -207,6 +207,9 @@ def NoImplicitFloat : EnumAttr<"noimplicitfloat", IntersectPreserve, [FnAttr]>;
 /// inline=never.
 def NoInline : EnumAttr<"noinline", IntersectPreserve, [FnAttr]>;
 
+/// nooutline
+def NoOutline : EnumAttr<"nooutline", IntersectPreserve, [FnAttr]>;
+
 /// Function is called early and/or often, so lazy binding isn't worthwhile.
 def NonLazyBind : EnumAttr<"nonlazybind", IntersectPreserve, [FnAttr]>;
 
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index aaee1f0a7687c..ab80da376fdf8 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -2257,6 +2257,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
     return Attribute::Captures;
   case bitc::ATTR_KIND_DEAD_ON_RETURN:
     return Attribute::DeadOnReturn;
+  case bitc::ATTR_KIND_NOOUTLINE:
+    return Attribute::NoOutline;
   }
 }
 
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index 54e916e2dcfe1..0efe7e030e0dc 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -956,6 +956,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
     return bitc::ATTR_KIND_CAPTURES;
   case Attribute::DeadOnReturn:
     return bitc::ATTR_KIND_DEAD_ON_RETURN;
+  case Attribute::NoOutline:
+    return bitc::ATTR_KIND_NOOUTLINE;
   case Attribute::EndAttrKinds:
     llvm_unreachable("Can not encode end-attribute kinds marker.");
   case Attribute::None:
diff --git a/llvm/lib/CodeGen/MachineOutliner.cpp b/llvm/lib/CodeGen/MachineOutliner.cpp
index 9feb9740de126..d6f7305278f38 100644
--- a/llvm/lib/CodeGen/MachineOutliner.cpp
+++ b/llvm/lib/CodeGen/MachineOutliner.cpp
@@ -1257,7 +1257,7 @@ void MachineOutliner::populateMapper(InstructionMapper &Mapper, Module &M) {
   for (Function &F : M) {
     LLVM_DEBUG(dbgs() << "MAPPING FUNCTION: " << F.getName() << "\n");
 
-    if (F.hasFnAttribute("nooutline")) {
+    if (F.hasFnAttribute(Attribute::NoOutline)) {
       LLVM_DEBUG(dbgs() << "SKIP: Function has nooutline attribute\n");
       continue;
     }
diff --git a/llvm/lib/IR/AutoUpgrade.cpp b/llvm/lib/IR/AutoUpgrade.cpp
index f28b98957cae4..f3b164e5d0603 100644
--- a/llvm/lib/IR/AutoUpgrade.cpp
+++ b/llvm/lib/IR/AutoUpgrade.cpp
@@ -5956,6 +5956,12 @@ void llvm::UpgradeFunctionAttributes(Function &F) {
     F.removeFnAttr("implicit-section-name");
   }
 
+  if (Attribute A = F.getFnAttribute("nooutline");
+      A.isValid() && A.isStringAttribute()) {
+    F.removeFnAttr("nooutline");
+    F.addFnAttr(Attribute::NoOutline);
+  }
+
   if (!F.empty()) {
     // For some reason this is called twice, and the first time is before any
     // instructions are loaded into the body.
diff --git a/llvm/lib/Transforms/IPO/IROutliner.cpp b/llvm/lib/Transforms/IPO/IROutliner.cpp
index fdf0c3ac8007d..cdcff8fb19236 100644
--- a/llvm/lib/Transforms/IPO/IROutliner.cpp
+++ b/llvm/lib/Transforms/IPO/IROutliner.cpp
@@ -2419,7 +2419,7 @@ void IROutliner::pruneIncompatibleRegions(
     if (FnForCurrCand.hasOptNone())
       continue;
 
-    if (FnForCurrCand.hasFnAttribute("nooutline")) {
+    if (FnForCurrCand.hasFnAttribute(Attribute::NoOutline)) {
       LLVM_DEBUG({
         dbgs() << "... Skipping function with nooutline attribute: "
                << FnForCurrCand.getName() << "\n";
diff --git a/llvm/test/Bitcode/upgrade-nooutline.ll b/llvm/test/Bitcode/upgrade-nooutline.ll
new file mode 100644
index 0000000000000..ac0168812bc42
--- /dev/null
+++ b/llvm/test/Bitcode/upgrade-nooutline.ll
@@ -0,0 +1,8 @@
+; RUN: llvm-as < %s | llvm-dis - | FileCheck %s --implicit-check-not=\"nooutline\"
+
+; CHECK: define void @f() [[ATTR:#[0-9]+]]
+; CHECK: attributes [[ATTR]] = { nooutline }
+
+define void @f() "nooutline" {
+  ret void
+}
diff --git a/llvm/test/CodeGen/AArch64/machine-outliner-mapper-debug-output.mir b/llvm/test/CodeGen/AArch64/machine-outliner-mapper-debug-output.mir
index 826157e68d75c..056c5f18d492c 100644
--- a/llvm/test/CodeGen/AArch64/machine-outliner-mapper-debug-output.mir
+++ b/llvm/test/CodeGen/AArch64/machine-outliner-mapper-debug-output.mir
@@ -17,7 +17,7 @@
 
 --- |
   define void @block_too_small() noredzone { unreachable }
-  define void @no_outline() noredzone "nooutline" { unreachable }
+  define void @no_outline() noredzone nooutline { unreachable }
   define void @redzone() { unreachable }
   declare void @no_mf()
   define void @block_addr_fn() noredzone {
diff --git a/llvm/test/Transforms/IROutliner/nooutline-attribute.ll b/llvm/test/Transforms/IROutliner/nooutline-attribute.ll
index eaf3afa3b15a1..a61a1614a3115 100644
--- a/llvm/test/Transforms/IROutliner/nooutline-attribute.ll
+++ b/llvm/test/Transforms/IROutliner/nooutline-attribute.ll
@@ -8,7 +8,7 @@
 
 define void @outlinable() { ret void }
 
-define i8 @nooutline1(ptr noalias %s, ptr noalias %d, i64 %len) "nooutline" {
+define i8 @nooutline1(ptr noalias %s, ptr noalias %d, i64 %len) nooutline {
   %a = load i8, ptr %s
   %b = load i8, ptr %d
   call void @llvm.memcpy.p0.p0.i64(ptr %d, ptr %s, i64 %len, i1 false)
@@ -17,7 +17,7 @@ define i8 @nooutline1(ptr noalias %s, ptr noalias %d, i64 %len) "nooutline" {
   ret i8 %ret
 }
 
-define i8 @nooutline2(ptr noalias %s, ptr noalias %d, i64 %len) "nooutline" {
+define i8 @nooutline2(ptr noalias %s, ptr noalias %d, i64 %len) nooutline {
   %a = load i8, ptr %s
   %b = load i8, ptr %d
   call void @llvm.memcpy.p0.p0.i64(ptr %d, ptr %s, i64 %len, i1 false)



More information about the cfe-commits mailing list