SIL: Introduce '@_alwaysEmitIntoClient' attribute for use by standard library

This is like '@inlinable', except that the symbol does not have a public
entry point in the generated binary at all; it is deserialized and a copy
is always emitted into the client binary, with shared linkage.

Just like '@inlinable', if you apply this to an internal declaration it
becomes '@usableFromInline' automatically.

This uses the same mechanism as default arguments ever since Swift 4, so
it should work reasonably well, but there are rough edges with diagnostics
and such. Don't use this if you are not the standard library.

Fixes <rdar://problem/33767512>, <https://bugs.swift.org/browse/SR-5646>.
This commit is contained in:
Slava Pestov
2019-02-15 20:41:59 -05:00
parent c2f8622aa8
commit bd6490b391
13 changed files with 79 additions and 11 deletions

View File

@@ -390,6 +390,9 @@ DECL_ATTR(_private, PrivateImport,
OnImport |
UserInaccessible |
NotSerialized, 82)
SIMPLE_DECL_ATTR(_alwaysEmitIntoClient, AlwaysEmitIntoClient,
OnVar | OnSubscript | OnAbstractFunction | UserInaccessible,
83)
#undef TYPE_ATTR
#undef DECL_ATTR_ALIAS

View File

@@ -4082,6 +4082,7 @@ ERROR(usable_from_inline_attr_in_protocol,none,
#define FRAGILE_FUNC_KIND \
"%select{a '@_transparent' function|" \
"an '@inlinable' function|" \
"an '@_alwaysEmitIntoClient' function|" \
"a default argument value|" \
"a property initializer in a '@_fixed_layout' type}"
@@ -4115,7 +4116,7 @@ NOTE(resilience_decl_declared_here,
ERROR(class_designated_init_inlinable_resilient,none,
"initializer for class %0 is "
"'%select{@_transparent|@inlinable|%error}1' and must "
"'%select{@_transparent|@inlinable|@_alwaysEmitIntoClient|%error}1' and must "
"delegate to another initializer", (Type, unsigned))
ERROR(attribute_invalid_on_stored_property,

View File

@@ -52,7 +52,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 473; // Last change: assign ownership qualifier
const uint16_t SWIFTMODULE_VERSION_MINOR = 474; // Last change: @_alwaysEmitIntoClient
using DeclIDField = BCFixed<31>;

View File

@@ -2518,12 +2518,14 @@ bool ValueDecl::isUsableFromInline() const {
assert(getFormalAccess() == AccessLevel::Internal);
if (getAttrs().hasAttribute<UsableFromInlineAttr>() ||
getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>() ||
getAttrs().hasAttribute<InlinableAttr>())
return true;
if (auto *accessor = dyn_cast<AccessorDecl>(this)) {
auto *storage = accessor->getStorage();
if (storage->getAttrs().hasAttribute<UsableFromInlineAttr>() ||
storage->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>() ||
storage->getAttrs().hasAttribute<InlinableAttr>())
return true;
}

View File

@@ -360,11 +360,18 @@ ResilienceExpansion DeclContext::getResilienceExpansion() const {
if (AFD->getAttrs().hasAttribute<InlinableAttr>())
return ResilienceExpansion::Minimal;
// If a property or subscript is @inlinable, the accessors are
// @inlinable also.
if (auto accessor = dyn_cast<AccessorDecl>(AFD))
if (accessor->getStorage()->getAttrs().getAttribute<InlinableAttr>())
if (AFD->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>())
return ResilienceExpansion::Minimal;
// If a property or subscript is @inlinable or @_alwaysEmitIntoClient,
// the accessors are @inlinable or @_alwaysEmitIntoClient also.
if (auto accessor = dyn_cast<AccessorDecl>(AFD)) {
auto *storage = accessor->getStorage();
if (storage->getAttrs().getAttribute<InlinableAttr>())
return ResilienceExpansion::Minimal;
if (storage->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>())
return ResilienceExpansion::Minimal;
}
}
}

View File

@@ -290,10 +290,18 @@ SILLinkage SILDeclRef::getLinkage(ForDefinition_t forDefinition) const {
/// or shared linkage.
OnDemand,
/// The declaration should never be made public.
NeverPublic
NeverPublic,
/// The declaration should always be emitted into the client,
AlwaysEmitIntoClient,
};
auto limit = Limit::None;
// @_alwaysEmitIntoClient declarations are like the default arguments of
// public functions; they are roots for dead code elimination and have
// serialized bodies, but no public symbol in the generated binary.
if (d->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>())
limit = Limit::AlwaysEmitIntoClient;
// ivar initializers and destroyers are completely contained within the class
// from which they come, and never get seen externally.
if (isIVarInitializerOrDestroyer()) {
@@ -369,6 +377,8 @@ SILLinkage SILDeclRef::getLinkage(ForDefinition_t forDefinition) const {
return SILLinkage::Shared;
if (limit == Limit::NeverPublic)
return maybeAddExternal(SILLinkage::Hidden);
if (limit == Limit::AlwaysEmitIntoClient)
return maybeAddExternal(SILLinkage::PublicNonABI);
return maybeAddExternal(SILLinkage::Public);
}
llvm_unreachable("unhandled access");
@@ -464,8 +474,8 @@ IsSerialized_t SILDeclRef::isSerialized() const {
auto *d = getDecl();
// Default argument generators are serialized if the function was
// type-checked in Swift 4 mode.
// Default argument generators are serialized if the containing
// declaration is public.
if (isDefaultArgGenerator()) {
ResilienceExpansion expansion;
if (auto *EED = dyn_cast<EnumElementDecl>(d)) {

View File

@@ -58,12 +58,21 @@ TypeChecker::getFragileFunctionKind(const DeclContext *DC) {
return std::make_pair(FragileFunctionKind::Inlinable,
/*treatUsableFromInlineAsPublic=*/true);
if (AFD->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>())
return std::make_pair(FragileFunctionKind::AlwaysEmitIntoClient,
/*treatUsableFromInlineAsPublic=*/true);
// If a property or subscript is @inlinable, the accessors are
// @inlinable also.
if (auto accessor = dyn_cast<AccessorDecl>(AFD))
if (accessor->getStorage()->getAttrs().getAttribute<InlinableAttr>())
if (auto accessor = dyn_cast<AccessorDecl>(AFD)) {
auto *storage = accessor->getStorage();
if (storage->getAttrs().getAttribute<InlinableAttr>())
return std::make_pair(FragileFunctionKind::Inlinable,
/*treatUsableFromInlineAsPublic=*/true);
if (storage->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>())
return std::make_pair(FragileFunctionKind::AlwaysEmitIntoClient,
/*treatUsableFromInlineAsPublic=*/true);
}
}
}

View File

@@ -72,6 +72,7 @@ public:
bool visitDeclAttribute(DeclAttribute *A) = delete;
#define IGNORED_ATTR(X) void visit##X##Attr(X##Attr *) {}
IGNORED_ATTR(AlwaysEmitIntoClient)
IGNORED_ATTR(Available)
IGNORED_ATTR(HasInitialValue)
IGNORED_ATTR(CDecl)
@@ -799,6 +800,7 @@ public:
void visit##CLASS##Attr(CLASS##Attr *) {}
IGNORED_ATTR(Alignment)
IGNORED_ATTR(AlwaysEmitIntoClient)
IGNORED_ATTR(Borrowed)
IGNORED_ATTR(HasInitialValue)
IGNORED_ATTR(ClangImporterSynthesizedType)

View File

@@ -1246,6 +1246,7 @@ namespace {
UNINTERESTING_ATTR(AccessControl)
UNINTERESTING_ATTR(Alignment)
UNINTERESTING_ATTR(AlwaysEmitIntoClient)
UNINTERESTING_ATTR(Borrowed)
UNINTERESTING_ATTR(CDecl)
UNINTERESTING_ATTR(Consuming)

View File

@@ -1925,6 +1925,7 @@ public:
enum class FragileFunctionKind : unsigned {
Transparent,
Inlinable,
AlwaysEmitIntoClient,
DefaultArgument,
PropertyInitializer
};

View File

@@ -0,0 +1 @@
@_alwaysEmitIntoClient public func alwaysEmitIntoClientOtherFunction() {}

View File

@@ -0,0 +1,13 @@
// RUN: %target-swift-emit-silgen -primary-file %s %S/Inputs/always_emit_into_client_other_file.swift | %FileCheck %s
// CHECK-LABEL: sil non_abi [serialized] [ossa] @$s33always_emit_into_client_attribute0A22EmitIntoClientFunctionyyF : $@convention(thin) () -> ()
@_alwaysEmitIntoClient public func alwaysEmitIntoClientFunction() {
alwaysEmitIntoClientOtherFunction()
}
// CHECK: sil hidden_external [serialized] @$s33always_emit_into_client_attribute0A27EmitIntoClientOtherFunctionyyF : $@convention(thin) () -> ()
// CHECK-LABEL: sil non_abi [serialized] [ossa] @$s33always_emit_into_client_attribute26implicitlyUsableFromInlineyyF : $@convention(thin) () -> ()
@_alwaysEmitIntoClient func implicitlyUsableFromInline() {
alwaysEmitIntoClientOtherFunction()
}

View File

@@ -0,0 +1,18 @@
// RUN: %target-typecheck-verify-swift
private func privateFunction() {}
// expected-note@-1{{global function 'privateFunction()' is not '@usableFromInline' or public}}
fileprivate func fileprivateFunction() {}
// expected-note@-1{{global function 'fileprivateFunction()' is not '@usableFromInline' or public}}
func internalFunction() {}
// expected-note@-1{{global function 'internalFunction()' is not '@usableFromInline' or public}}
@usableFromInline func versionedFunction() {}
public func publicFunction() {}
@_alwaysEmitIntoClient public func alwaysEmitIntoClientFunction() {
privateFunction() // expected-error {{global function 'privateFunction()' is private and cannot be referenced from an '@_alwaysEmitIntoClient' function}}
fileprivateFunction() // expected-error {{global function 'fileprivateFunction()' is fileprivate and cannot be referenced from an '@_alwaysEmitIntoClient' function}}
internalFunction() // expected-error {{global function 'internalFunction()' is internal and cannot be referenced from an '@_alwaysEmitIntoClient' function}}
versionedFunction()
publicFunction()
}