Skip to content

Add @_used and @_section attributes for global variables and top-level functions #65901

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/ReferenceGuides/UnderscoredAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,12 @@ Fully bypasses access control, allowing access to private declarations
in the imported module. The imported module needs to be compiled with
`-Xfrontend -enable-private-imports` for this to work.

## `@_section("section_name")`

Places a global variable or a top-level function into a section of the object
file with the given name. It's the equivalent of clang's
`__attribute__((section))`.

## `@_semantics("uniquely.recognized.id")`

Allows the optimizer to make use of some key invariants in performance critical
Expand Down Expand Up @@ -994,6 +1000,12 @@ for more details.

This `async` function uses the pre-SE-0338 semantics of unsafely inheriting the caller's executor. This is an underscored feature because the right way of inheriting an executor is to pass in the required executor and switch to it. Unfortunately, there are functions in the standard library which need to inherit their caller's executor but cannot change their ABI because they were not defined as `@_alwaysEmitIntoClient` in the initial release.

## `@_used`

Marks a global variable or a top-level function as "used externally" even if it
does not have visible users in the compilation unit. It's the equivalent of
clang's `__attribute__((used))`.

## `@_weakLinked`

Allows a declaration to be weakly-referenced, i.e., any references emitted by
Expand Down
18 changes: 18 additions & 0 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,24 @@ class SILGenNameAttr : public DeclAttribute {
}
};

/// Defines the @_section attribute.
class SectionAttr : public DeclAttribute {
public:
SectionAttr(StringRef Name, SourceLoc AtLoc, SourceRange Range, bool Implicit)
: DeclAttribute(DAK_Section, AtLoc, Range, Implicit),
Name(Name) {}

SectionAttr(StringRef Name, bool Implicit)
: SectionAttr(Name, SourceLoc(), SourceRange(), Implicit) {}

/// The section name.
const StringRef Name;

static bool classof(const DeclAttribute *DA) {
return DA->getKind() == DAK_Section;
}
};

/// Defines the @_cdecl attribute.
class CDeclAttr : public DeclAttribute {
public:
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsSIL.def
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ ERROR(performance_unknown_callees,none,
"called function is not known at compile time and can have unpredictable performance", ())
ERROR(performance_callee_unavailable,none,
"called function is not available in this module and can have unpredictable performance", ())
ERROR(section_attr_on_non_const_global,none,
"global variable must be a compile-time constant to use @_section attribute", ())
NOTE(performance_called_from,none,
"called from here", ())

Expand Down
10 changes: 10 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1702,6 +1702,16 @@ ERROR(cdecl_empty_name,none,
ERROR(cdecl_throws,none,
"raising errors from @_cdecl functions is not supported", ())

// @_used and @_section
ERROR(section_linkage_markers_disabled,none,
"attribute requires '-enable-experimental-feature SymbolLinkageMarkers'", ())
ERROR(used_not_at_top_level,none,
"@_used can only be applied to global functions and variables", ())
ERROR(section_not_at_top_level,none,
"@_section can only be applied to global functions and variables", ())
ERROR(section_empty_name,none,
"@_section section name cannot be empty", ())

ERROR(expose_only_non_other_attr,none,
"@_expose attribute cannot be applied to an '%0' declaration", (StringRef))
ERROR(expose_inside_unexposed_decl,none,
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ EXPERIMENTAL_FEATURE(FlowSensitiveConcurrencyCaptures, false)
EXPERIMENTAL_FEATURE(CodeItemMacros, true)
EXPERIMENTAL_FEATURE(TupleConformances, false)

// Whether to enable @_used and @_section attributes
EXPERIMENTAL_FEATURE(SymbolLinkageMarkers, true)

// FIXME: MoveOnlyClasses is not intended to be in production,
// but our tests currently rely on it, and we want to run those
// tests in non-asserts builds too.
Expand Down
14 changes: 14 additions & 0 deletions include/swift/SIL/SILFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,9 @@ class SILFunction
/// The function's remaining set of specialize attributes.
std::vector<SILSpecializeAttr*> SpecializeAttrSet;

/// Name of a section if @_section attribute was used, otherwise empty.
StringRef Section;

/// Has value if there's a profile for this function
/// Contains Function Entry Count
ProfileCounter EntryCount;
Expand Down Expand Up @@ -346,6 +349,9 @@ class SILFunction
/// would indicate.
unsigned HasCReferences : 1;

/// Whether attribute @_used was present
unsigned MarkedAsUsed : 1;

/// Whether cross-module references to this function should always use weak
/// linking.
unsigned IsAlwaysWeakImported : 1;
Expand Down Expand Up @@ -1234,6 +1240,14 @@ class SILFunction
return V && V->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>();
}

/// Return whether this function has attribute @_used on it
bool markedAsUsed() const { return MarkedAsUsed; }
void setMarkedAsUsed(bool value) { MarkedAsUsed = value; }

/// Return custom section name if @_section was used, otherwise empty
StringRef section() const { return Section; }
void setSection(StringRef value) { Section = value; }

/// Returns true if this function belongs to a declaration that returns
/// an opaque result type with one or more availability conditions that are
/// allowed to produce a different underlying type at runtime.
Expand Down
15 changes: 15 additions & 0 deletions include/swift/SIL/SILGlobalVariable.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,21 @@ class SILGlobalVariable
StaticInitializerBlock.eraseAllInstructions(Module);
}

/// Returns true if this global variable has `@_used` attribute.
bool markedAsUsed() const {
auto *V = getDecl();
return V && V->getAttrs().hasAttribute<UsedAttr>();
}

/// Returns a SectionAttr if this global variable has `@_section` attribute.
SectionAttr *getSectionAttr() const {
auto *V = getDecl();
if (!V)
return nullptr;

return V->getAttrs().getAttribute<SectionAttr>();
}

/// Return whether this variable corresponds to a Clang node.
bool hasClangNode() const;

Expand Down
11 changes: 11 additions & 0 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3211,6 +3211,17 @@ static bool usesFeatureTupleConformances(Decl *decl) {
return false;
}

static bool usesFeatureSymbolLinkageMarkers(Decl *decl) {
auto &attrs = decl->getAttrs();
return std::any_of(attrs.begin(), attrs.end(), [](auto *attr) {
if (isa<UsedAttr>(attr))
return true;
if (isa<SectionAttr>(attr))
return true;
return false;
});
}

static bool usesFeatureLayoutPrespecialization(Decl *decl) {
auto &attrs = decl->getAttrs();
return std::any_of(attrs.begin(), attrs.end(), [](auto *attr) {
Expand Down
7 changes: 7 additions & 0 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,11 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
Printer << ")";
break;

case DAK_Section:
Printer.printAttrName("@_section");
Printer << "(\"" << cast<SectionAttr>(this)->Name << "\")";
break;

case DAK_ObjC: {
Printer.printAttrName("@objc");
llvm::SmallString<32> scratch;
Expand Down Expand Up @@ -1602,6 +1607,8 @@ StringRef DeclAttribute::getAttrName() const {
return "backDeployed";
case DAK_Expose:
return "_expose";
case DAK_Section:
return "_section";
case DAK_Documentation:
return "_documentation";
case DAK_MacroRole:
Expand Down
12 changes: 12 additions & 0 deletions lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2699,6 +2699,12 @@ Address IRGenModule::getAddrOfSILGlobalVariable(SILGlobalVariable *var,
}
if (!forDefinition)
gvar->setComdat(nullptr);

// Mark as llvm.used if @_used, set section if @_section
if (var->markedAsUsed())
addUsedGlobal(gvar);
if (auto *sectionAttr = var->getSectionAttr())
gvar->setSection(sectionAttr->Name);
}
if (forDefinition && !gvar->hasInitializer()) {
if (initVal) {
Expand Down Expand Up @@ -3468,6 +3474,12 @@ llvm::Function *IRGenModule::getAddrOfSILFunction(
fn = createFunction(*this, link, signature, insertBefore,
f->getOptimizationMode(), shouldEmitStackProtector(f));

// Mark as llvm.used if @_used, set section if @_section
if (f->markedAsUsed())
addUsedGlobal(fn);
if (!f->section().empty())
fn->setSection(f->section());

// If `hasCReferences` is true, then the function is either marked with
// @_silgen_name OR @_cdecl. If it is the latter, it must have a definition
// associated with it. The combination of the two allows us to identify the
Expand Down
40 changes: 40 additions & 0 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2937,6 +2937,46 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,

break;
}

case DAK_Section: {
if (!consumeIf(tok::l_paren)) {
diagnose(Loc, diag::attr_expected_lparen, AttrName,
DeclAttribute::isDeclModifier(DK));
return makeParserSuccess();
}

if (Tok.isNot(tok::string_literal)) {
diagnose(Loc, diag::attr_expected_string_literal, AttrName);
return makeParserSuccess();
}

auto Name = getStringLiteralIfNotInterpolated(
Loc, ("'" + AttrName + "'").str());

consumeToken(tok::string_literal);

if (Name.has_value())
AttrRange = SourceRange(Loc, Tok.getRange().getStart());
else
DiscardAttribute = true;

if (!consumeIf(tok::r_paren)) {
diagnose(Loc, diag::attr_expected_rparen, AttrName,
DeclAttribute::isDeclModifier(DK));
return makeParserSuccess();
}

// @_section in a local scope is not allowed.
if (CurDeclContext->isLocalContext()) {
diagnose(Loc, diag::attr_only_at_non_local_scope, AttrName);
}

if (!DiscardAttribute)
Attributes.add(new (Context) SectionAttr(Name.value(), AtLoc,
AttrRange, /*Implicit=*/false));

break;
}

case DAK_Alignment: {
if (!consumeIf(tok::l_paren)) {
Expand Down
6 changes: 6 additions & 0 deletions lib/SIL/IR/SILFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ void SILFunction::init(
this->InlineStrategy = inlineStrategy;
this->Linkage = unsigned(Linkage);
this->HasCReferences = false;
this->MarkedAsUsed = false;
this->IsAlwaysWeakImported = false;
this->IsDynamicReplaceable = isDynamic;
this->ExactSelfClass = isExactSelfClass;
Expand Down Expand Up @@ -280,11 +281,13 @@ void SILFunction::createSnapshot(int id) {
newSnapshot->ObjCReplacementFor = ObjCReplacementFor;
newSnapshot->SemanticsAttrSet = SemanticsAttrSet;
newSnapshot->SpecializeAttrSet = SpecializeAttrSet;
newSnapshot->Section = Section;
newSnapshot->Availability = Availability;
newSnapshot->specialPurpose = specialPurpose;
newSnapshot->perfConstraints = perfConstraints;
newSnapshot->GlobalInitFlag = GlobalInitFlag;
newSnapshot->HasCReferences = HasCReferences;
newSnapshot->MarkedAsUsed = MarkedAsUsed;
newSnapshot->IsAlwaysWeakImported = IsAlwaysWeakImported;
newSnapshot->HasOwnership = HasOwnership;
newSnapshot->IsWithoutActuallyEscapingThunk = IsWithoutActuallyEscapingThunk;
Expand Down Expand Up @@ -858,6 +861,9 @@ SILFunction::isPossiblyUsedExternally() const {
if (isRuntimeAccessible())
return true;

if (markedAsUsed())
return true;

// Declaration marked as `@_alwaysEmitIntoClient` that
// returns opaque result type with availability conditions
// has to be kept alive to emit opaque type metadata descriptor.
Expand Down
9 changes: 9 additions & 0 deletions lib/SIL/IR/SILFunctionBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ void SILFunctionBuilder::addFunctionAttributes(
if (Attrs.hasAttribute<SILGenNameAttr>() || Attrs.hasAttribute<CDeclAttr>())
F->setHasCReferences(true);

if (Attrs.hasAttribute<UsedAttr>())
F->setMarkedAsUsed(true);

if (Attrs.hasAttribute<NoLocksAttr>()) {
F->setPerfConstraints(PerformanceConstraints::NoLocks);
} else if (Attrs.hasAttribute<NoAllocationAttr>()) {
Expand Down Expand Up @@ -195,6 +198,12 @@ void SILFunctionBuilder::addFunctionAttributes(
return;
auto *decl = constant.getDecl();

// Don't add section for addressor functions (where decl is a global)
if (isa<FuncDecl>(decl)) {
if (auto *SA = Attrs.getAttribute<SectionAttr>())
F->setSection(SA->Name);
}

// Only emit replacements for the objc entry point of objc methods.
// There is one exception: @_dynamicReplacement(for:) of @objc methods in
// generic classes. In this special case we use native replacement instead of
Expand Down
6 changes: 6 additions & 0 deletions lib/SIL/IR/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3236,6 +3236,12 @@ void SILFunction::print(SILPrintContext &PrintCtx) const {
OS << "[_specialize "; Attr->print(OS); OS << "] ";
}

if (markedAsUsed())
OS << "[used] ";

if (!section().empty())
OS << "[section \"" << section() << "\"] ";

// TODO: Handle clang node owners which don't have a name.
if (hasClangNode() && getClangNodeOwner()->hasName()) {
OS << "[clang ";
Expand Down
Loading