From 8afdacba9dcd36fc838eb86fca86f7f903040030 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Fri, 26 Feb 2021 11:59:53 -0800 Subject: [PATCH] Add GNU attribute 'retain' For ELF targets, GCC 11 will set SHF_GNU_RETAIN on the section of a `__attribute__((retain))` function/variable to prevent linker garbage collection. (See AttrDocs.td for the linker support). This patch adds `retain` functions/variables to the `llvm.used` list, which has the desired linker GC semantics. Note: `retain` does not imply `used`, so an unused function/variable can be dropped by Sema. Before 'retain' was introduced, previous ELF solutions require inline asm or linker tricks, e.g. `asm volatile(".reloc 0, R_X86_64_NONE, target");` (architecture dependent) or define a non-local symbol in the section and use `ld -u`. There was no elegant source-level solution. With D97448, `__attribute__((retain))` will set `SHF_GNU_RETAIN` on ELF targets. Differential Revision: https://reviews.llvm.org/D97447 --- clang/include/clang/Basic/Attr.td | 9 ++++- clang/include/clang/Basic/AttrDocs.td | 49 +++++++++++++++++++++++++++ clang/lib/CodeGen/CGDecl.cpp | 4 ++- clang/lib/CodeGen/CodeGenModule.cpp | 4 +++ clang/lib/Sema/SemaDecl.cpp | 14 +++++++- clang/test/CodeGen/attr-retain.c | 29 ++++++++++++++++ clang/test/CodeGenCXX/attr-retain.cpp | 45 ++++++++++++++++++++++++ clang/test/Sema/attr-retain.c | 29 ++++++++++++++++ 8 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 clang/test/CodeGen/attr-retain.c create mode 100644 clang/test/CodeGenCXX/attr-retain.cpp create mode 100644 clang/test/Sema/attr-retain.c diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index bc2d8ceeeb6c..8afa676c133f 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2648,7 +2648,14 @@ def Unused : InheritableAttr { def Used : InheritableAttr { let Spellings = [GCC<"used">]; let Subjects = SubjectList<[NonLocalVar, Function, ObjCMethod]>; - let Documentation = [Undocumented]; + let Documentation = [UsedDocs]; + let SimpleHandler = 1; +} + +def Retain : InheritableAttr { + let Spellings = [GCC<"retain">]; + let Subjects = SubjectList<[NonLocalVar, Function, ObjCMethod]>; + let Documentation = [RetainDocs]; let SimpleHandler = 1; } diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index b88b18d37477..deda68b64f90 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -57,6 +57,55 @@ global variable or function should be in after translation. let Heading = "section, __declspec(allocate)"; } +def UsedDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +This attribute, when attached to a function or variable definition, indicates +that there may be references to the entity which are not apparent in the source +code. For example, it may be referenced from inline ``asm``, or it may be +found through a dynamic symbol or section lookup. + +The compiler must emit the definition even if it appears to be unused, and it +must not apply optimizations which depend on fully understanding how the entity +is used. + +Whether this attribute has any effect on the linker depends on the target and +the linker. Most linkers support the feature of section garbage collection +(``--gc-sections``), also known as "dead stripping" (``ld64 -dead_strip``) or +discarding unreferenced sections (``link.exe /OPT:REF``). On COFF and Mach-O +targets (Windows and Apple platforms), the `used` attribute prevents symbols +from being removed by linker section GC. On ELF targets, it has no effect on its +own, and the linker may remove the definition if it is not otherwise referenced. +This linker GC can be avoided by also adding the ``retain`` attribute. Note +that ``retain`` requires special support from the linker; see that attribute's +documentation for further information. + }]; +} + +def RetainDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +This attribute, when attached to a function or variable definition, prevents +section garbage collection in the linker. It does not prevent other discard +mechanisms, such as archive member selection, and COMDAT group resolution. + +If the compiler does not emit the definition, e.g. because it was not used in +the translation unit or the compiler was able to eliminate all of the uses, +this attribute has no effect. This attribute is typically combined with the +``used`` attribute to force the definition to be emitted and preserved into the +final linked image. + +This attribute is only necessary on ELF targets; other targets prevent section +garbage collection by the linker when using the ``used`` attribute alone. +Using the attributes together should result in consistent behavior across +targets. + +This attribute requires the linker to support the ``SHF_GNU_RETAIN`` extension. +This support is available in GNU ``ld`` and ``gold`` as of binutils 2.36, as +well as in ``ld.lld`` 13. + }]; +} + def InitPriorityDocs : Documentation { let Category = DocCatVariable; let Content = [{ diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index ecf79dbbaffc..5a691ee303e4 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -441,7 +441,9 @@ void CodeGenFunction::EmitStaticVarDecl(const VarDecl &D, if (const SectionAttr *SA = D.getAttr()) var->setSection(SA->getName()); - if (D.hasAttr()) + if (D.hasAttr()) + CGM.addUsedGlobal(var); + else if (D.hasAttr()) CGM.addUsedOrCompilerUsedGlobal(var); // We may have to cast the constant because of the initializer diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 564b07582376..0d499a564039 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -1896,6 +1896,8 @@ void CodeGenModule::setNonAliasAttributes(GlobalDecl GD, if (D) { if (auto *GV = dyn_cast(GO)) { + if (D->hasAttr()) + addUsedGlobal(GV); if (auto *SA = D->getAttr()) GV->addAttribute("bss-section", SA->getName()); if (auto *SA = D->getAttr()) @@ -1907,6 +1909,8 @@ void CodeGenModule::setNonAliasAttributes(GlobalDecl GD, } if (auto *F = dyn_cast(GO)) { + if (D->hasAttr()) + addUsedGlobal(F); if (auto *SA = D->getAttr()) if (!D->getAttr()) F->addFnAttr("implicit-section-name", SA->getName()); diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 50a29e29eb54..64321f4880c3 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2831,6 +2831,11 @@ void Sema::mergeDeclAttributes(NamedDecl *New, Decl *Old, NewAttr->setInherited(true); New->addAttr(NewAttr); } + if (RetainAttr *OldAttr = Old->getMostRecentDecl()->getAttr()) { + RetainAttr *NewAttr = OldAttr->clone(Context); + NewAttr->setInherited(true); + New->addAttr(NewAttr); + } if (!Old->hasAttrs() && !New->hasAttrs()) return; @@ -2953,7 +2958,7 @@ void Sema::mergeDeclAttributes(NamedDecl *New, Decl *Old, } // Already handled. - if (isa(I)) + if (isa(I) || isa(I)) continue; if (mergeDeclAttribute(*this, New, I, LocalAMK)) @@ -13304,6 +13309,13 @@ void Sema::FinalizeDeclaration(Decl *ThisDecl) { VD->dropAttr(); } } + if (RetainAttr *Attr = VD->getAttr()) { + if (!Attr->isInherited() && !VD->isThisDeclarationADefinition()) { + Diag(Attr->getLocation(), diag::warn_attribute_ignored_on_non_definition) + << Attr; + VD->dropAttr(); + } + } const DeclContext *DC = VD->getDeclContext(); // If there's a #pragma GCC visibility in scope, and this isn't a class diff --git a/clang/test/CodeGen/attr-retain.c b/clang/test/CodeGen/attr-retain.c new file mode 100644 index 000000000000..871065d4fe75 --- /dev/null +++ b/clang/test/CodeGen/attr-retain.c @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -emit-llvm %s -o - | FileCheck %s + +/// Set !retain regardless of the target. The backend will lower !retain to +/// SHF_GNU_RETAIN on ELF and ignore the metadata for other binary formats. +// CHECK: @c0 ={{.*}} constant i32 {{.*}} +// CHECK: @foo.l0 = internal global i32 {{.*}} +// CHECK: @g0 ={{.*}} global i32 {{.*}} +// CHECK-NEXT: @g1 ={{.*}} global i32 {{.*}} +// CHECK-NEXT: @g3 = internal global i32 {{.*}} +// CHECK-NEXT: @g4 = internal global i32 0, section ".data.g"{{.*}} + +// CHECK: @llvm.used = appending global [8 x i8*] [i8* bitcast (i32* @c0 to i8*), i8* bitcast (i32* @foo.l0 to i8*), i8* bitcast (void ()* @f0 to i8*), i8* bitcast (void ()* @f2 to i8*), i8* bitcast (i32* @g0 to i8*), i8* bitcast (i32* @g1 to i8*), i8* bitcast (i32* @g3 to i8*), i8* bitcast (i32* @g4 to i8*)], section "llvm.metadata" +// CHECK: @llvm.compiler.used = appending global [3 x i8*] [i8* bitcast (void ()* @f2 to i8*), i8* bitcast (i32* @g3 to i8*), i8* bitcast (i32* @g4 to i8*)], section "llvm.metadata" + +const int c0 __attribute__((retain)) = 42; + +void foo() { + static int l0 __attribute__((retain)) = 2; +} + +__attribute__((retain)) int g0; +int g1 __attribute__((retain)); +__attribute__((retain)) static int g2; +__attribute__((used, retain)) static int g3; +__attribute__((used, retain, section(".data.g"))) static int g4; + +void __attribute__((retain)) f0(void) {} +static void __attribute__((retain)) f1(void) {} +static void __attribute__((used, retain)) f2(void) {} diff --git a/clang/test/CodeGenCXX/attr-retain.cpp b/clang/test/CodeGenCXX/attr-retain.cpp new file mode 100644 index 000000000000..0bc1a4c95abb --- /dev/null +++ b/clang/test/CodeGenCXX/attr-retain.cpp @@ -0,0 +1,45 @@ +// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple -Werror %s -o - | FileCheck %s + +// CHECK: @llvm.used = appending global [7 x i8*] +// CHECK-SAME: @_ZN2X0C2Ev +// CHECK-SAME: @_ZN2X0C1Ev +// CHECK-SAME: @_ZN2X0D2Ev +// CHECK-SAME: @_ZN2X0D1Ev +// CHECK-SAME: @_ZN2X16Nested2f1Ev +// CHECK-SAME: @_ZN10merge_declL4funcEv +// CHECK-SAME: @_ZN18instantiate_member1SIiE1fEv + +struct X0 { + // CHECK: define linkonce_odr{{.*}} @_ZN2X0C1Ev({{.*}} + __attribute__((used, retain)) X0() {} + // CHECK: define linkonce_odr{{.*}} @_ZN2X0D1Ev({{.*}} + __attribute__((used, retain)) ~X0() {} +}; + +struct X1 { + struct Nested { + // CHECK-NOT: 2f0Ev + // CHECK: define linkonce_odr{{.*}} @_ZN2X16Nested2f1Ev({{.*}} + void __attribute__((retain)) f0() {} + void __attribute__((used, retain)) f1() {} + }; +}; + +// CHECK: define internal void @_ZN10merge_declL4funcEv(){{.*}} +namespace merge_decl { +static void func(); +void bar() { void func() __attribute__((used, retain)); } +static void func() {} +} // namespace merge_decl + +namespace instantiate_member { +template +struct S { + void __attribute__((used, retain)) f() {} +}; + +void test() { + // CHECK: define linkonce_odr{{.*}} void @_ZN18instantiate_member1SIiE1fEv({{.*}} + S a; +} +} // namespace instantiate_member diff --git a/clang/test/Sema/attr-retain.c b/clang/test/Sema/attr-retain.c new file mode 100644 index 000000000000..478059b5fcc3 --- /dev/null +++ b/clang/test/Sema/attr-retain.c @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s -Wunused-function + +/// We allow 'retain' on non-ELF targets because 'retain' is often used together +/// with 'used'. 'used' has GC root semantics on macOS and Windows. We want +/// users to just write retain,used and don't need to dispatch on binary formats. + +extern char test1[] __attribute__((retain)); // expected-warning {{'retain' attribute ignored on a non-definition declaration}} +extern const char test2[] __attribute__((retain)); // expected-warning {{'retain' attribute ignored on a non-definition declaration}} +const char test3[] __attribute__((retain)) = ""; + +struct __attribute__((retain)) s { // expected-warning {{'retain' attribute only applies to variables with non-local storage, functions, and Objective-C methods}} +}; + +void foo() { + static int a __attribute__((retain)); + int b __attribute__((retain)); // expected-warning {{'retain' attribute only applies to variables with non-local storage, functions, and Objective-C methods}} + (void)a; + (void)b; +} + +__attribute__((retain,used)) static void f0() {} +__attribute__((retain)) static void f1() {} // expected-warning {{unused function 'f1'}} +__attribute__((retain)) void f2() {} + +/// Test attribute merging. +int tentative; +int tentative __attribute__((retain)); +extern int tentative; +int tentative = 0;