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
This commit is contained in:
Fangrui Song 2021-02-26 11:59:53 -08:00
parent 233ba2709b
commit 8afdacba9d
8 changed files with 180 additions and 3 deletions

View File

@ -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;
}

View File

@ -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 = [{

View File

@ -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

View File

@ -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());

View File

@ -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

View File

@ -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) {}

View File

@ -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

View File

@ -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;