mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[SIL] Refactor linkage computation
- Factor out the adding of 'external' for non-definitions - Factor out computation of the linkage limit Intended to be NFC.
This commit is contained in:
@@ -380,7 +380,10 @@ struct SILDeclRef {
|
|||||||
bool isAlwaysInline() const;
|
bool isAlwaysInline() const;
|
||||||
/// True if the function has the @_backDeploy attribute.
|
/// True if the function has the @_backDeploy attribute.
|
||||||
bool isBackDeployed() const;
|
bool isBackDeployed() const;
|
||||||
|
|
||||||
|
/// Return the expected linkage for a definition of this declaration.
|
||||||
|
SILLinkage getDefinitionLinkage() const;
|
||||||
|
|
||||||
/// Return the expected linkage of this declaration.
|
/// Return the expected linkage of this declaration.
|
||||||
SILLinkage getLinkage(ForDefinition_t forDefinition) const;
|
SILLinkage getLinkage(ForDefinition_t forDefinition) const;
|
||||||
|
|
||||||
|
|||||||
@@ -284,16 +284,157 @@ bool SILDeclRef::isImplicit() const {
|
|||||||
llvm_unreachable("Unhandled case in switch");
|
llvm_unreachable("Unhandled case in switch");
|
||||||
}
|
}
|
||||||
|
|
||||||
SILLinkage SILDeclRef::getLinkage(ForDefinition_t forDefinition) const {
|
namespace {
|
||||||
|
enum class LinkageLimit {
|
||||||
|
/// No limit.
|
||||||
|
None,
|
||||||
|
/// The linkage should behave as if the decl is private.
|
||||||
|
Private,
|
||||||
|
/// The declaration is emitted on-demand; it should end up with internal
|
||||||
|
/// or shared linkage.
|
||||||
|
OnDemand,
|
||||||
|
/// The declaration should never be made public.
|
||||||
|
NeverPublic,
|
||||||
|
/// The declaration should always be emitted into the client,
|
||||||
|
AlwaysEmitIntoClient,
|
||||||
|
};
|
||||||
|
} // end anonymous namespace
|
||||||
|
|
||||||
|
/// Compute the linkage limit for a given SILDeclRef. This augments the
|
||||||
|
/// mapping of access level to linkage to provide a maximum or minimum linkage.
|
||||||
|
static LinkageLimit getLinkageLimit(SILDeclRef constant) {
|
||||||
|
using Limit = LinkageLimit;
|
||||||
|
using Kind = SILDeclRef::Kind;
|
||||||
|
|
||||||
|
auto *d = constant.getDecl();
|
||||||
|
|
||||||
|
// Back deployment thunks and fallbacks are emitted into the client.
|
||||||
|
if (constant.backDeploymentKind != SILDeclRef::BackDeploymentKind::None)
|
||||||
|
return Limit::AlwaysEmitIntoClient;
|
||||||
|
|
||||||
|
if (auto *fn = dyn_cast<AbstractFunctionDecl>(d)) {
|
||||||
|
// Native-to-foreign thunks for top-level decls are created on-demand,
|
||||||
|
// unless they are marked @_cdecl, in which case they expose a dedicated
|
||||||
|
// entry-point with the visibility of the function.
|
||||||
|
//
|
||||||
|
// Native-to-foreign thunks for methods are always just private, since
|
||||||
|
// they're anchored by Objective-C metadata.
|
||||||
|
auto &attrs = fn->getAttrs();
|
||||||
|
if (constant.isNativeToForeignThunk() && !attrs.hasAttribute<CDeclAttr>()) {
|
||||||
|
auto isTopLevel = fn->getDeclContext()->isModuleScopeContext();
|
||||||
|
return isTopLevel ? Limit::OnDemand : Limit::Private;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto fn = constant.getFuncDecl()) {
|
||||||
|
// Forced-static-dispatch functions are created on-demand and have
|
||||||
|
// at best shared linkage.
|
||||||
|
if (fn->hasForcedStaticDispatch())
|
||||||
|
return Limit::OnDemand;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (constant.kind) {
|
||||||
|
case Kind::Func:
|
||||||
|
case Kind::Allocator:
|
||||||
|
case Kind::Initializer:
|
||||||
|
case Kind::Deallocator:
|
||||||
|
case Kind::Destroyer: {
|
||||||
|
// @_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>())
|
||||||
|
return Limit::AlwaysEmitIntoClient;
|
||||||
|
if (auto accessor = dyn_cast<AccessorDecl>(d)) {
|
||||||
|
auto *storage = accessor->getStorage();
|
||||||
|
if (storage->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>())
|
||||||
|
return Limit::AlwaysEmitIntoClient;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Kind::EnumElement:
|
||||||
|
return Limit::OnDemand;
|
||||||
|
|
||||||
|
case Kind::GlobalAccessor:
|
||||||
|
return cast<VarDecl>(d)->isResilient() ? Limit::NeverPublic : Limit::None;
|
||||||
|
|
||||||
|
case Kind::DefaultArgGenerator:
|
||||||
|
// If the default argument is to be serialized, only use non-ABI public
|
||||||
|
// linkage. If the argument is not to be serialized, don't use a limit.
|
||||||
|
// This actually means that default arguments *can be ABI public* if
|
||||||
|
// `isSerialized()` returns false and the effective access level is public,
|
||||||
|
// which happens under `-enable-testing` with an internal decl.
|
||||||
|
return constant.isSerialized() ? Limit::AlwaysEmitIntoClient : Limit::None;
|
||||||
|
|
||||||
|
case Kind::PropertyWrapperBackingInitializer:
|
||||||
|
case Kind::PropertyWrapperInitFromProjectedValue: {
|
||||||
|
if (!d->getDeclContext()->isTypeContext()) {
|
||||||
|
// If the backing initializer is to be serialized, only use non-ABI public
|
||||||
|
// linkage. If the initializer is not to be serialized, don't use a limit.
|
||||||
|
// This actually means that it *can be ABI public* if `isSerialized()`
|
||||||
|
// returns false and the effective access level is public, which happens
|
||||||
|
// under `-enable-testing` with an internal decl.
|
||||||
|
return constant.isSerialized() ? Limit::AlwaysEmitIntoClient
|
||||||
|
: Limit::None;
|
||||||
|
}
|
||||||
|
// Otherwise, regular property wrapper backing initializers (for properties)
|
||||||
|
// are treated just like stored property intializers.
|
||||||
|
LLVM_FALLTHROUGH;
|
||||||
|
}
|
||||||
|
case Kind::StoredPropertyInitializer: {
|
||||||
|
// Stored property initializers get the linkage of their containing type.
|
||||||
|
// There are three cases:
|
||||||
|
//
|
||||||
|
// 1) Type is formally @_fixed_layout/@frozen. Root initializers can be
|
||||||
|
// declared @inlinable. The property initializer must only reference
|
||||||
|
// public symbols, and is serialized, so we give it PublicNonABI linkage.
|
||||||
|
//
|
||||||
|
// 2) Type is not formally @_fixed_layout/@frozen and the module is not
|
||||||
|
// resilient. Root initializers can be declared @inlinable. This is the
|
||||||
|
// annoying case. We give the initializer public linkage if the type is
|
||||||
|
// public.
|
||||||
|
//
|
||||||
|
// 3) Type is resilient. The property initializer is never public because
|
||||||
|
// root initializers cannot be @inlinable.
|
||||||
|
//
|
||||||
|
// FIXME: Get rid of case 2 somehow.
|
||||||
|
if (constant.isSerialized())
|
||||||
|
return Limit::AlwaysEmitIntoClient;
|
||||||
|
|
||||||
|
// FIXME: This should always be true.
|
||||||
|
if (d->getModuleContext()->isResilient())
|
||||||
|
return Limit::NeverPublic;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Kind::IVarInitializer:
|
||||||
|
case Kind::IVarDestroyer:
|
||||||
|
// ivar initializers and destroyers are completely contained within the
|
||||||
|
// class from which they come, and never get seen externally.
|
||||||
|
return Limit::NeverPublic;
|
||||||
|
|
||||||
|
case Kind::EntryPoint:
|
||||||
|
case Kind::AsyncEntryPoint:
|
||||||
|
llvm_unreachable("Already handled");
|
||||||
|
}
|
||||||
|
return Limit::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
SILLinkage SILDeclRef::getDefinitionLinkage() const {
|
||||||
|
using Limit = LinkageLimit;
|
||||||
|
|
||||||
|
auto privateLinkage = [&]() {
|
||||||
|
// Private decls may still be serialized if they are e.g in an inlinable
|
||||||
|
// function. In such a case, they receive shared linkage.
|
||||||
|
return isSerialized() ? SILLinkage::Shared : SILLinkage::Private;
|
||||||
|
};
|
||||||
|
|
||||||
// Prespecializations are public.
|
// Prespecializations are public.
|
||||||
if (getSpecializedSignature()) {
|
if (getSpecializedSignature())
|
||||||
return SILLinkage::Public;
|
return SILLinkage::Public;
|
||||||
}
|
|
||||||
|
|
||||||
if (getAbstractClosureExpr()) {
|
// Closures can only be referenced from the same file.
|
||||||
return isSerialized() ? SILLinkage::Shared : SILLinkage::Private;
|
if (getAbstractClosureExpr())
|
||||||
}
|
return privateLinkage();
|
||||||
|
|
||||||
// The main entry-point is public.
|
// The main entry-point is public.
|
||||||
if (kind == Kind::EntryPoint)
|
if (kind == Kind::EntryPoint)
|
||||||
@@ -301,29 +442,6 @@ SILLinkage SILDeclRef::getLinkage(ForDefinition_t forDefinition) const {
|
|||||||
if (kind == Kind::AsyncEntryPoint)
|
if (kind == Kind::AsyncEntryPoint)
|
||||||
return SILLinkage::Hidden;
|
return SILLinkage::Hidden;
|
||||||
|
|
||||||
// Add External to the linkage (e.g. Public -> PublicExternal) if this is a
|
|
||||||
// declaration not a definition.
|
|
||||||
auto maybeAddExternal = [&](SILLinkage linkage) {
|
|
||||||
return forDefinition ? linkage : addExternalToLinkage(linkage);
|
|
||||||
};
|
|
||||||
|
|
||||||
ValueDecl *d = getDecl();
|
|
||||||
|
|
||||||
// Property wrapper generators of public functions have PublicNonABI linkage
|
|
||||||
if (isPropertyWrapperBackingInitializer() && isa<ParamDecl>(d)) {
|
|
||||||
if (isSerialized())
|
|
||||||
return maybeAddExternal(SILLinkage::PublicNonABI);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function-local declarations have private linkage, unless serialized.
|
|
||||||
DeclContext *moduleContext = d->getDeclContext();
|
|
||||||
while (!moduleContext->isModuleScopeContext()) {
|
|
||||||
if (moduleContext->isLocalContext()) {
|
|
||||||
return isSerialized() ? SILLinkage::Shared : SILLinkage::Private;
|
|
||||||
}
|
|
||||||
moduleContext = moduleContext->getParent();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calling convention thunks have shared linkage.
|
// Calling convention thunks have shared linkage.
|
||||||
if (isForeignToNativeThunk())
|
if (isForeignToNativeThunk())
|
||||||
return SILLinkage::Shared;
|
return SILLinkage::Shared;
|
||||||
@@ -332,143 +450,81 @@ SILLinkage SILDeclRef::getLinkage(ForDefinition_t forDefinition) const {
|
|||||||
if (isClangImported())
|
if (isClangImported())
|
||||||
return SILLinkage::Shared;
|
return SILLinkage::Shared;
|
||||||
|
|
||||||
// Default argument generators of Public functions have PublicNonABI linkage
|
const auto limit = getLinkageLimit(*this);
|
||||||
// if the function was type-checked in Swift 4 mode.
|
if (limit == Limit::Private)
|
||||||
if (kind == SILDeclRef::Kind::DefaultArgGenerator) {
|
return privateLinkage();
|
||||||
if (isSerialized())
|
|
||||||
return maybeAddExternal(SILLinkage::PublicNonABI);
|
auto *decl = getDecl();
|
||||||
|
|
||||||
|
if (isPropertyWrapperBackingInitializer()) {
|
||||||
|
auto *dc = decl->getDeclContext();
|
||||||
|
|
||||||
|
// Property wrapper generators of public functions have PublicNonABI
|
||||||
|
// linkage.
|
||||||
|
if (isa<ParamDecl>(decl) && isSerialized())
|
||||||
|
return SILLinkage::PublicNonABI;
|
||||||
|
|
||||||
|
// Property wrappers in types have linkage based on the access level of
|
||||||
|
// their nominal.
|
||||||
|
if (dc->isTypeContext())
|
||||||
|
decl = cast<NominalTypeDecl>(dc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Back deployment thunks and fallbacks are emitted into the client and
|
// Stored property initializers have linkage based on the access level of
|
||||||
// therefore have PublicNonABI linkage.
|
// their nominal.
|
||||||
if (backDeploymentKind != SILDeclRef::BackDeploymentKind::None)
|
if (isStoredPropertyInitializer())
|
||||||
return maybeAddExternal(SILLinkage::PublicNonABI);
|
decl = cast<NominalTypeDecl>(decl->getDeclContext());
|
||||||
|
|
||||||
enum class Limit {
|
// Compute the effective access level, taking e.g testable into consideration.
|
||||||
/// No limit.
|
auto effectiveAccess = decl->getEffectiveAccess();
|
||||||
None,
|
|
||||||
/// The declaration is emitted on-demand; it should end up with internal
|
|
||||||
/// or shared linkage.
|
|
||||||
OnDemand,
|
|
||||||
/// The declaration should never be made public.
|
|
||||||
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;
|
|
||||||
if (auto accessor = dyn_cast<AccessorDecl>(d)) {
|
|
||||||
auto *storage = accessor->getStorage();
|
|
||||||
if (storage->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()) {
|
|
||||||
limit = Limit::NeverPublic;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stored property initializers get the linkage of their containing type.
|
|
||||||
if (isStoredPropertyInitializer() || isPropertyWrapperBackingInitializer()) {
|
|
||||||
// Three cases:
|
|
||||||
//
|
|
||||||
// 1) Type is formally @_fixed_layout/@frozen. Root initializers can be
|
|
||||||
// declared @inlinable. The property initializer must only reference
|
|
||||||
// public symbols, and is serialized, so we give it PublicNonABI linkage.
|
|
||||||
//
|
|
||||||
// 2) Type is not formally @_fixed_layout/@frozen and the module is not
|
|
||||||
// resilient. Root initializers can be declared @inlinable. This is the
|
|
||||||
// annoying case. We give the initializer public linkage if the type is
|
|
||||||
// public.
|
|
||||||
//
|
|
||||||
// 3) Type is resilient. The property initializer is never public because
|
|
||||||
// root initializers cannot be @inlinable.
|
|
||||||
//
|
|
||||||
// FIXME: Get rid of case 2 somehow.
|
|
||||||
if (isSerialized())
|
|
||||||
return maybeAddExternal(SILLinkage::PublicNonABI);
|
|
||||||
|
|
||||||
d = cast<NominalTypeDecl>(d->getDeclContext());
|
|
||||||
|
|
||||||
// FIXME: This should always be true.
|
|
||||||
if (d->getModuleContext()->isResilient())
|
|
||||||
limit = Limit::NeverPublic;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The global addressor is never public for resilient globals.
|
|
||||||
if (kind == Kind::GlobalAccessor) {
|
|
||||||
if (cast<VarDecl>(d)->isResilient()) {
|
|
||||||
limit = Limit::NeverPublic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto fn = dyn_cast<FuncDecl>(d)) {
|
|
||||||
// Forced-static-dispatch functions are created on-demand and have
|
|
||||||
// at best shared linkage.
|
|
||||||
if (fn->hasForcedStaticDispatch()) {
|
|
||||||
limit = Limit::OnDemand;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto fn = dyn_cast<AbstractFunctionDecl>(d)) {
|
|
||||||
// Native-to-foreign thunks for top-level decls are created on-demand,
|
|
||||||
// unless they are marked @_cdecl, in which case they expose a dedicated
|
|
||||||
// entry-point with the visibility of the function.
|
|
||||||
//
|
|
||||||
// Native-to-foreign thunks for methods are always just private, since
|
|
||||||
// they're anchored by Objective-C metadata.
|
|
||||||
if (isNativeToForeignThunk() && !fn->getAttrs().hasAttribute<CDeclAttr>()) {
|
|
||||||
if (fn->getDeclContext()->isModuleScopeContext())
|
|
||||||
limit = Limit::OnDemand;
|
|
||||||
else
|
|
||||||
return SILLinkage::Private;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEnumElement()) {
|
|
||||||
limit = Limit::OnDemand;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto effectiveAccess = d->getEffectiveAccess();
|
|
||||||
|
|
||||||
// Private setter implementations for an internal storage declaration should
|
// Private setter implementations for an internal storage declaration should
|
||||||
// be at least internal as well, so that a dynamically-writable
|
// be at least internal as well, so that a dynamically-writable
|
||||||
// keypath can be formed from other files in the same module.
|
// keypath can be formed from other files in the same module.
|
||||||
if (auto accessor = dyn_cast<AccessorDecl>(d)) {
|
if (auto *accessor = dyn_cast<AccessorDecl>(decl)) {
|
||||||
if (accessor->isSetter()
|
auto storageAccess = accessor->getStorage()->getEffectiveAccess();
|
||||||
&& accessor->getStorage()->getEffectiveAccess() >= AccessLevel::Internal)
|
if (accessor->isSetter() && storageAccess >= AccessLevel::Internal)
|
||||||
effectiveAccess = std::max(effectiveAccess, AccessLevel::Internal);
|
effectiveAccess = std::max(effectiveAccess, AccessLevel::Internal);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (effectiveAccess) {
|
switch (effectiveAccess) {
|
||||||
case AccessLevel::Private:
|
case AccessLevel::Private:
|
||||||
case AccessLevel::FilePrivate:
|
case AccessLevel::FilePrivate:
|
||||||
return SILLinkage::Private;
|
return privateLinkage();
|
||||||
|
|
||||||
case AccessLevel::Internal:
|
case AccessLevel::Internal:
|
||||||
|
assert(!isSerialized() &&
|
||||||
|
"Serialized decls should either be private (for decls in inlinable "
|
||||||
|
"code), or they should be public");
|
||||||
if (limit == Limit::OnDemand)
|
if (limit == Limit::OnDemand)
|
||||||
return SILLinkage::Shared;
|
return SILLinkage::Shared;
|
||||||
return maybeAddExternal(SILLinkage::Hidden);
|
return SILLinkage::Hidden;
|
||||||
|
|
||||||
case AccessLevel::Public:
|
case AccessLevel::Public:
|
||||||
case AccessLevel::Open:
|
case AccessLevel::Open:
|
||||||
if (limit == Limit::OnDemand)
|
switch (limit) {
|
||||||
|
case Limit::None:
|
||||||
|
return SILLinkage::Public;
|
||||||
|
case Limit::AlwaysEmitIntoClient:
|
||||||
|
return SILLinkage::PublicNonABI;
|
||||||
|
case Limit::OnDemand:
|
||||||
return SILLinkage::Shared;
|
return SILLinkage::Shared;
|
||||||
if (limit == Limit::NeverPublic)
|
case Limit::NeverPublic:
|
||||||
return maybeAddExternal(SILLinkage::Hidden);
|
return SILLinkage::Hidden;
|
||||||
if (limit == Limit::AlwaysEmitIntoClient)
|
case Limit::Private:
|
||||||
return maybeAddExternal(SILLinkage::PublicNonABI);
|
llvm_unreachable("Already handled");
|
||||||
return maybeAddExternal(SILLinkage::Public);
|
}
|
||||||
}
|
}
|
||||||
llvm_unreachable("unhandled access");
|
llvm_unreachable("unhandled access");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SILLinkage SILDeclRef::getLinkage(ForDefinition_t forDefinition) const {
|
||||||
|
// Add external to the linkage of the definition
|
||||||
|
// (e.g. Public -> PublicExternal) if this is a declaration.
|
||||||
|
auto linkage = getDefinitionLinkage();
|
||||||
|
return forDefinition ? linkage : addExternalToLinkage(linkage);
|
||||||
|
}
|
||||||
|
|
||||||
SILDeclRef SILDeclRef::getDefaultArgGenerator(Loc loc,
|
SILDeclRef SILDeclRef::getDefaultArgGenerator(Loc loc,
|
||||||
unsigned defaultArgIndex) {
|
unsigned defaultArgIndex) {
|
||||||
SILDeclRef result;
|
SILDeclRef result;
|
||||||
|
|||||||
@@ -29,3 +29,21 @@ public struct S {
|
|||||||
set { }
|
set { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class C {
|
||||||
|
// C.__allocating_init()
|
||||||
|
// CHECK-LABEL: sil non_abi [serialized] [exact_self_class] [ossa] @$s33always_emit_into_client_attribute1CCACycfC : $@convention(method) (@thick C.Type) -> @owned C
|
||||||
|
|
||||||
|
// C.init()
|
||||||
|
// CHECK-LABEL: sil non_abi [serialized] [ossa] @$s33always_emit_into_client_attribute1CCACycfc : $@convention(method) (@owned C) -> @owned C
|
||||||
|
@_alwaysEmitIntoClient
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
// C.deinit
|
||||||
|
// CHECK-LABEL: sil non_abi [serialized] [ossa] @$s33always_emit_into_client_attribute1CCfd : $@convention(method) (@guaranteed C) -> @owned Builtin.NativeObject
|
||||||
|
|
||||||
|
// C.__deallocating_deinit
|
||||||
|
// CHECK-LABEL: sil non_abi [serialized] [ossa] @$s33always_emit_into_client_attribute1CCfD : $@convention(method) (@owned C) -> ()
|
||||||
|
@_alwaysEmitIntoClient
|
||||||
|
deinit {}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user