[clang] [llvm] [clang] Add flatten_deep attribute for depth-limited inlining (1/3) (PR #165777)

Grigory Pastukhov via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 31 10:42:21 PDT 2025


https://github.com/grigorypas updated https://github.com/llvm/llvm-project/pull/165777

>From f84132355882ef7d909e2970f6dc251efd4785f4 Mon Sep 17 00:00:00 2001
From: Grigory Pastukhov <gpastukhov at meta.com>
Date: Fri, 24 Oct 2025 12:58:33 -0700
Subject: [PATCH 1/2] Add flatten_deep attribute to clang

---
 clang/include/clang/Basic/Attr.td             |  7 ++++++
 clang/include/clang/Basic/AttrDocs.td         | 23 +++++++++++++++++++
 clang/lib/Sema/SemaDeclAttr.cpp               | 21 +++++++++++++++++
 ...a-attribute-supported-attributes-list.test |  1 +
 clang/test/Sema/attr-flatten-deep.c           | 14 +++++++++++
 5 files changed, 66 insertions(+)
 create mode 100644 clang/test/Sema/attr-flatten-deep.c

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 749f531ec9ab1..1ccd659e49e63 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1984,6 +1984,13 @@ def Flatten : InheritableAttr {
   let SimpleHandler = 1;
 }
 
+def FlattenDeep : InheritableAttr {
+  let Spellings = [Clang<"flatten_deep">];
+  let Subjects = SubjectList<[Function], ErrorDiag>;
+  let Args = [UnsignedArgument<"MaxDepth">];
+  let Documentation = [FlattenDeepDocs];
+}
+
 def Format : InheritableAttr {
   let Spellings = [GCC<"format">];
   let Args = [IdentifierArgument<"Type">, IntArgument<"FormatIdx">,
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 2fdd041c1b46e..f4280531019f5 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -4032,6 +4032,29 @@ callee is unavailable or if the callee has the ``noinline`` attribute.
   }];
 }
 
+def FlattenDeepDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+The ``flatten_deep`` attribute causes calls within the attributed function and
+their transitive callees to be inlined up to a specified depth, unless it is
+impossible to do so (for example if the body of the callee is unavailable or if
+the callee has the ``noinline`` attribute).
+
+This attribute takes a single unsigned integer argument representing the maximum
+depth of the call tree to inline. For example, ``__attribute__((flatten_deep(3)))``
+will inline all calls within the function, then inline all calls within those
+inlined functions (depth 2), and then inline all calls within those functions
+(depth 3).
+
+.. code-block:: c++
+
+  __attribute__((flatten_deep(3)))
+  void process_data() {
+    // All calls up to 3 levels deep in the call tree will be inlined
+  }
+  }];
+}
+
 def FormatDocs : Documentation {
   let Category = DocCatFunction;
   let Content = [{
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 964a2a791e18f..1a78dfce6e1f3 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -3695,6 +3695,24 @@ static void handleInitPriorityAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   D->addAttr(::new (S.Context) InitPriorityAttr(S.Context, AL, prioritynum));
 }
 
+static void handleFlattenDeepAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+  Expr *E = AL.getArgAsExpr(0);
+  uint32_t maxDepth;
+  if (!S.checkUInt32Argument(AL, E, maxDepth)) {
+    AL.setInvalid();
+    return;
+  }
+
+  if (maxDepth == 0) {
+    S.Diag(AL.getLoc(), diag::err_attribute_argument_is_zero)
+        << AL << E->getSourceRange();
+    AL.setInvalid();
+    return;
+  }
+
+  D->addAttr(::new (S.Context) FlattenDeepAttr(S.Context, AL, maxDepth));
+}
+
 ErrorAttr *Sema::mergeErrorAttr(Decl *D, const AttributeCommonInfo &CI,
                                 StringRef NewUserDiagnostic) {
   if (const auto *EA = D->getAttr<ErrorAttr>()) {
@@ -7236,6 +7254,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_Format:
     handleFormatAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_FlattenDeep:
+    handleFlattenDeepAttr(S, D, AL);
+    break;
   case ParsedAttr::AT_FormatMatches:
     handleFormatMatchesAttr(S, D, AL);
     break;
diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index ab4153a64f028..da6152dbff3a5 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -86,6 +86,7 @@
 // CHECK-NEXT: ExternalSourceSymbol ((SubjectMatchRule_record, SubjectMatchRule_enum, SubjectMatchRule_enum_constant, SubjectMatchRule_field, SubjectMatchRule_function, SubjectMatchRule_namespace, SubjectMatchRule_objc_category, SubjectMatchRule_objc_implementation, SubjectMatchRule_objc_interface, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_objc_protocol, SubjectMatchRule_record, SubjectMatchRule_type_alias, SubjectMatchRule_variable))
 // CHECK-NEXT: FlagEnum (SubjectMatchRule_enum)
 // CHECK-NEXT: Flatten (SubjectMatchRule_function)
+// CHECK-NEXT: FlattenDeep (SubjectMatchRule_function)
 // CHECK-NEXT: FunctionReturnThunks (SubjectMatchRule_function)
 // CHECK-NEXT: GNUInline (SubjectMatchRule_function)
 // CHECK-NEXT: HIPManaged (SubjectMatchRule_variable)
diff --git a/clang/test/Sema/attr-flatten-deep.c b/clang/test/Sema/attr-flatten-deep.c
new file mode 100644
index 0000000000000..92bc792424332
--- /dev/null
+++ b/clang/test/Sema/attr-flatten-deep.c
@@ -0,0 +1,14 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+// Test basic usage - valid
+__attribute__((flatten_deep(3)))
+void test_valid() {
+}
+
+// Test attribute on non-function - should error
+__attribute__((flatten_deep(3))) int x; // expected-error {{'flatten_deep' attribute only applies to functions}}
+
+// Test depth = 0 - should error (depth must be >= 1)
+__attribute__((flatten_deep(0))) // expected-error {{'flatten_deep' attribute must be greater than 0}}
+void test_depth_zero() {
+}

>From b92b7cf09b28ef6ec71ec444660b99d5ebf67b0c Mon Sep 17 00:00:00 2001
From: Grigory Pastukhov <gpastukhov at meta.com>
Date: Fri, 24 Oct 2025 13:53:35 -0700
Subject: [PATCH 2/2] Add flatten_deep attribute in LLVM

---
 clang/lib/CodeGen/CodeGenModule.cpp          |  9 +++++
 clang/test/CodeGen/attr-flatten-deep-cg.c    | 10 +++++
 llvm/include/llvm/AsmParser/LLParser.h       |  1 +
 llvm/include/llvm/Bitcode/LLVMBitCodes.h     |  1 +
 llvm/include/llvm/IR/Attributes.h            |  4 ++
 llvm/include/llvm/IR/Attributes.td           |  3 ++
 llvm/lib/AsmParser/LLParser.cpp              | 41 ++++++++++++++++++++
 llvm/lib/Bitcode/Reader/BitcodeReader.cpp    |  4 ++
 llvm/lib/Bitcode/Writer/BitcodeWriter.cpp    |  2 +
 llvm/lib/IR/Attributes.cpp                   | 10 +++++
 llvm/lib/Transforms/Utils/CodeExtractor.cpp  |  1 +
 llvm/test/Bitcode/attributes-flatten-deep.ll | 32 +++++++++++++++
 12 files changed, 118 insertions(+)
 create mode 100644 clang/test/CodeGen/attr-flatten-deep-cg.c
 create mode 100644 llvm/test/Bitcode/attributes-flatten-deep.ll

diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 0fea57b2e1799..e872bb65111ae 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -2749,6 +2749,15 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D,
       B.addAttribute("aarch64_new_zt0");
   }
 
+  // Handle flatten_deep attribute for depth-based inlining
+  if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
+    if (const FlattenDeepAttr *FDA = FD->getAttr<FlattenDeepAttr>()) {
+      // Add the flatten_deep attribute with the max depth value as a typed int
+      // attribute
+      B.addRawIntAttr(llvm::Attribute::FlattenDeep, FDA->getMaxDepth());
+    }
+  }
+
   // Track whether we need to add the optnone LLVM attribute,
   // starting with the default for this optimization level.
   bool ShouldAddOptNone =
diff --git a/clang/test/CodeGen/attr-flatten-deep-cg.c b/clang/test/CodeGen/attr-flatten-deep-cg.c
new file mode 100644
index 0000000000000..a75bb434f8f9f
--- /dev/null
+++ b/clang/test/CodeGen/attr-flatten-deep-cg.c
@@ -0,0 +1,10 @@
+// RUN: %clang_cc1 -emit-llvm -o - %s | FileCheck %s
+
+// CHECK-LABEL: define {{.*}} @test1
+// CHECK-SAME: #[[ATTR1:[0-9]+]]
+__attribute__((flatten_deep(3)))
+void test1() {
+}
+
+// Verify the attribute is present in the attribute groups
+// CHECK-DAG: attributes #[[ATTR1]] = { {{.*}}flatten_deep=3{{.*}} }
diff --git a/llvm/include/llvm/AsmParser/LLParser.h b/llvm/include/llvm/AsmParser/LLParser.h
index 9eb31d7e0a451..8fba1d21f4758 100644
--- a/llvm/include/llvm/AsmParser/LLParser.h
+++ b/llvm/include/llvm/AsmParser/LLParser.h
@@ -320,6 +320,7 @@ namespace llvm {
                                 bool AllowParens = false);
     bool parseOptionalCodeModel(CodeModel::Model &model);
     bool parseOptionalDerefAttrBytes(lltok::Kind AttrKind, uint64_t &Bytes);
+    bool parseOptionalFlattenDeepDepth(lltok::Kind AttrKind, uint64_t &Depth);
     bool parseOptionalUWTableKind(UWTableKind &Kind);
     bool parseAllocKind(AllocFnKind &Kind);
     std::optional<MemoryEffects> parseMemoryAttr();
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index 464f475098ec5..48cb6fba2923a 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_FLATTEN_DEEP = 105,
 };
 
 enum ComdatSelectionKindCodes {
diff --git a/llvm/include/llvm/IR/Attributes.h b/llvm/include/llvm/IR/Attributes.h
index e734466ce20e0..efeb4be2baee4 100644
--- a/llvm/include/llvm/IR/Attributes.h
+++ b/llvm/include/llvm/IR/Attributes.h
@@ -1246,6 +1246,10 @@ class AttrBuilder {
   /// form used internally in Attribute.
   LLVM_ABI AttrBuilder &addDereferenceableOrNullAttr(uint64_t Bytes);
 
+  /// This turns the flatten_deep depth into the form used internally in
+  /// Attribute.
+  LLVM_ABI AttrBuilder &addFlattenDeepAttr(uint64_t Depth);
+
   /// This turns one (or two) ints into the form used internally in Attribute.
   LLVM_ABI AttrBuilder &
   addAllocSizeAttr(unsigned ElemSizeArg,
diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index 8ce2b1bea8fac..ed501f5ec9522 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]>;
 
+/// Inline calls recursively up to specified depth.
+def FlattenDeep : IntAttr<"flatten_deep", 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/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index 8e3ce4990f437..1390d46dcf2bf 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -1620,6 +1620,23 @@ bool LLParser::parseEnumAttribute(Attribute::AttrKind Attr, AttrBuilder &B,
     B.addDereferenceableOrNullAttr(Bytes);
     return false;
   }
+  case Attribute::FlattenDeep: {
+    uint64_t Depth = 0;
+    if (InAttrGroup) {
+      Lex.Lex();
+      LocTy DepthLoc = Lex.getLoc();
+      if (parseToken(lltok::equal, "expected '=' here") ||
+          parseUInt64(Depth))
+        return true;
+      if (!Depth)
+        return error(DepthLoc, "flatten_deep depth must be non-zero");
+    } else {
+      if (parseOptionalFlattenDeepDepth(lltok::kw_flatten_deep, Depth))
+        return true;
+    }
+    B.addFlattenDeepAttr(Depth);
+    return false;
+  }
   case Attribute::UWTable: {
     UWTableKind Kind;
     if (parseOptionalUWTableKind(Kind))
@@ -2494,6 +2511,30 @@ bool LLParser::parseOptionalDerefAttrBytes(lltok::Kind AttrKind,
   return false;
 }
 
+/// parseOptionalFlattenDeepDepth
+///   ::= /* empty */
+///   ::= 'flatten_deep' '(' 4 ')'
+bool LLParser::parseOptionalFlattenDeepDepth(lltok::Kind AttrKind,
+                                             uint64_t &Depth) {
+  assert(AttrKind == lltok::kw_flatten_deep && "contract!");
+
+  Depth = 0;
+  if (!EatIfPresent(AttrKind))
+    return false;
+  LocTy ParenLoc = Lex.getLoc();
+  if (!EatIfPresent(lltok::lparen))
+    return error(ParenLoc, "expected '('");
+  LocTy DepthLoc = Lex.getLoc();
+  if (parseUInt64(Depth))
+    return true;
+  ParenLoc = Lex.getLoc();
+  if (!EatIfPresent(lltok::rparen))
+    return error(ParenLoc, "expected ')'");
+  if (!Depth)
+    return error(DepthLoc, "flatten_deep depth must be non-zero");
+  return false;
+}
+
 bool LLParser::parseOptionalUWTableKind(UWTableKind &Kind) {
   Lex.Lex();
   Kind = UWTableKind::Default;
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index 466dcb02696f4..2a8a0937bcdb2 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -2077,6 +2077,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
     return Attribute::DisableSanitizerInstrumentation;
   case bitc::ATTR_KIND_ELEMENTTYPE:
     return Attribute::ElementType;
+  case bitc::ATTR_KIND_FLATTEN_DEEP:
+    return Attribute::FlattenDeep;
   case bitc::ATTR_KIND_FNRETTHUNK_EXTERN:
     return Attribute::FnRetThunkExtern;
   case bitc::ATTR_KIND_INLINE_HINT:
@@ -2391,6 +2393,8 @@ Error BitcodeReader::parseAttributeGroupBlock() {
             B.addDereferenceableAttr(Record[++i]);
           else if (Kind == Attribute::DereferenceableOrNull)
             B.addDereferenceableOrNullAttr(Record[++i]);
+          else if (Kind == Attribute::FlattenDeep)
+            B.addFlattenDeepAttr(Record[++i]);
           else if (Kind == Attribute::AllocSize)
             B.addAllocSizeAttrFromRawRepr(Record[++i]);
           else if (Kind == Attribute::VScaleRange)
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index f17656c7c3b03..13ed5d10ce041 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::FlattenDeep:
+    return bitc::ATTR_KIND_FLATTEN_DEEP;
   case Attribute::EndAttrKinds:
     llvm_unreachable("Can not encode end-attribute kinds marker.");
   case Attribute::None:
diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp
index 4ac2ebd55dcac..0bc838932e144 100644
--- a/llvm/lib/IR/Attributes.cpp
+++ b/llvm/lib/IR/Attributes.cpp
@@ -574,6 +574,9 @@ std::string Attribute::getAsString(bool InAttrGrp) const {
   if (hasAttribute(Attribute::DereferenceableOrNull))
     return AttrWithBytesToString("dereferenceable_or_null");
 
+  if (hasAttribute(Attribute::FlattenDeep))
+    return AttrWithBytesToString("flatten_deep");
+
   if (hasAttribute(Attribute::AllocSize)) {
     unsigned ElemSize;
     std::optional<unsigned> NumElems;
@@ -2206,6 +2209,13 @@ AttrBuilder &AttrBuilder::addDereferenceableOrNullAttr(uint64_t Bytes) {
   return addRawIntAttr(Attribute::DereferenceableOrNull, Bytes);
 }
 
+AttrBuilder &AttrBuilder::addFlattenDeepAttr(uint64_t Depth) {
+  if (Depth == 0)
+    return *this;
+
+  return addRawIntAttr(Attribute::FlattenDeep, Depth);
+}
+
 AttrBuilder &
 AttrBuilder::addAllocSizeAttr(unsigned ElemSize,
                               const std::optional<unsigned> &NumElems) {
diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
index 5ba6f95f5fae8..ea3f381565c12 100644
--- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp
+++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
@@ -936,6 +936,7 @@ Function *CodeExtractor::constructFunctionDeclaration(
         continue;
       // Those attributes should be safe to propagate to the extracted function.
       case Attribute::AlwaysInline:
+      case Attribute::FlattenDeep:
       case Attribute::Cold:
       case Attribute::DisableSanitizerInstrumentation:
       case Attribute::FnRetThunkExtern:
diff --git a/llvm/test/Bitcode/attributes-flatten-deep.ll b/llvm/test/Bitcode/attributes-flatten-deep.ll
new file mode 100644
index 0000000000000..c6d07161831f8
--- /dev/null
+++ b/llvm/test/Bitcode/attributes-flatten-deep.ll
@@ -0,0 +1,32 @@
+; RUN: llvm-as < %s | llvm-dis | FileCheck %s
+; RUN: verify-uselistorder %s
+
+; Test that flatten_deep attribute with integer values is properly handled
+; in both attribute groups (flatten_deep=N syntax) and inline (flatten_deep(N) syntax)
+
+; Test inline syntax
+; CHECK: define void @test_inline() #0
+define void @test_inline() flatten_deep(5) {
+  ret void
+}
+
+; Test attribute group alone
+; CHECK: define void @test_group_alone() #1
+define void @test_group_alone() #1 {
+  ret void
+}
+
+; Test attribute group with other attributes
+; CHECK: define void @test_group_combined() #2
+define void @test_group_combined() #2 {
+  ret void
+}
+
+; CHECK: attributes #0 = { flatten_deep=5 }
+attributes #0 = { flatten_deep=5 }
+
+; CHECK: attributes #1 = { flatten_deep=3 }
+attributes #1 = { flatten_deep=3 }
+
+; CHECK: attributes #2 = { noinline nounwind flatten_deep=7 }
+attributes #2 = { noinline nounwind flatten_deep=7 }



More information about the cfe-commits mailing list