[clang] 8afdacb - Add GNU attribute 'retain'

Fangrui Song via cfe-commits cfe-commits at lists.llvm.org
Fri Feb 26 16:38:50 PST 2021


Author: Fangrui Song
Date: 2021-02-26T16:37:50-08:00
New Revision: 8afdacba9dcd36fc838eb86fca86f7f903040030

URL: https://github.com/llvm/llvm-project/commit/8afdacba9dcd36fc838eb86fca86f7f903040030
DIFF: https://github.com/llvm/llvm-project/commit/8afdacba9dcd36fc838eb86fca86f7f903040030.diff

LOG: 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

Added: 
    clang/test/CodeGen/attr-retain.c
    clang/test/CodeGenCXX/attr-retain.cpp
    clang/test/Sema/attr-retain.c

Modified: 
    clang/include/clang/Basic/Attr.td
    clang/include/clang/Basic/AttrDocs.td
    clang/lib/CodeGen/CGDecl.cpp
    clang/lib/CodeGen/CodeGenModule.cpp
    clang/lib/Sema/SemaDecl.cpp

Removed: 
    


################################################################################
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<SectionAttr>())
     var->setSection(SA->getName());
 
-  if (D.hasAttr<UsedAttr>())
+  if (D.hasAttr<RetainAttr>())
+    CGM.addUsedGlobal(var);
+  else if (D.hasAttr<UsedAttr>())
     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<llvm::GlobalVariable>(GO)) {
+      if (D->hasAttr<RetainAttr>())
+        addUsedGlobal(GV);
       if (auto *SA = D->getAttr<PragmaClangBSSSectionAttr>())
         GV->addAttribute("bss-section", SA->getName());
       if (auto *SA = D->getAttr<PragmaClangDataSectionAttr>())
@@ -1907,6 +1909,8 @@ void CodeGenModule::setNonAliasAttributes(GlobalDecl GD,
     }
 
     if (auto *F = dyn_cast<llvm::Function>(GO)) {
+      if (D->hasAttr<RetainAttr>())
+        addUsedGlobal(F);
       if (auto *SA = D->getAttr<PragmaClangTextSectionAttr>())
         if (!D->getAttr<SectionAttr>())
           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>()) {
+    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<UsedAttr>(I))
+    if (isa<UsedAttr>(I) || isa<RetainAttr>(I))
       continue;
 
     if (mergeDeclAttribute(*this, New, I, LocalAMK))
@@ -13304,6 +13309,13 @@ void Sema::FinalizeDeclaration(Decl *ThisDecl) {
       VD->dropAttr<UsedAttr>();
     }
   }
+  if (RetainAttr *Attr = VD->getAttr<RetainAttr>()) {
+    if (!Attr->isInherited() && !VD->isThisDeclarationADefinition()) {
+      Diag(Attr->getLocation(), diag::warn_attribute_ignored_on_non_definition)
+          << Attr;
+      VD->dropAttr<RetainAttr>();
+    }
+  }
 
   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 <typename T>
+struct S {
+  void __attribute__((used, retain)) f() {}
+};
+
+void test() {
+  // CHECK: define linkonce_odr{{.*}} void @_ZN18instantiate_member1SIiE1fEv({{.*}}
+  S<int> 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;


        


More information about the cfe-commits mailing list