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 <[email protected]> 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) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
