diff --git a/include/swift/SIL/SILDeclRef.h b/include/swift/SIL/SILDeclRef.h index 71d4ba05d64..3f7bdb36944 100644 --- a/include/swift/SIL/SILDeclRef.h +++ b/include/swift/SIL/SILDeclRef.h @@ -380,7 +380,10 @@ struct SILDeclRef { bool isAlwaysInline() const; /// True if the function has the @_backDeploy attribute. bool isBackDeployed() const; - + + /// Return the expected linkage for a definition of this declaration. + SILLinkage getDefinitionLinkage() const; + /// Return the expected linkage of this declaration. SILLinkage getLinkage(ForDefinition_t forDefinition) const; diff --git a/lib/SIL/IR/SILDeclRef.cpp b/lib/SIL/IR/SILDeclRef.cpp index a42e4d10e49..1834f97ce4d 100644 --- a/lib/SIL/IR/SILDeclRef.cpp +++ b/lib/SIL/IR/SILDeclRef.cpp @@ -284,16 +284,157 @@ bool SILDeclRef::isImplicit() const { 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(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()) { + 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()) + return Limit::AlwaysEmitIntoClient; + if (auto accessor = dyn_cast(d)) { + auto *storage = accessor->getStorage(); + if (storage->getAttrs().hasAttribute()) + return Limit::AlwaysEmitIntoClient; + } + break; + } + case Kind::EnumElement: + return Limit::OnDemand; + + case Kind::GlobalAccessor: + return cast(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. - if (getSpecializedSignature()) { + if (getSpecializedSignature()) return SILLinkage::Public; - } - if (getAbstractClosureExpr()) { - return isSerialized() ? SILLinkage::Shared : SILLinkage::Private; - } + // Closures can only be referenced from the same file. + if (getAbstractClosureExpr()) + return privateLinkage(); // The main entry-point is public. if (kind == Kind::EntryPoint) @@ -301,29 +442,6 @@ SILLinkage SILDeclRef::getLinkage(ForDefinition_t forDefinition) const { if (kind == Kind::AsyncEntryPoint) 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(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. if (isForeignToNativeThunk()) return SILLinkage::Shared; @@ -332,143 +450,81 @@ SILLinkage SILDeclRef::getLinkage(ForDefinition_t forDefinition) const { if (isClangImported()) return SILLinkage::Shared; - // Default argument generators of Public functions have PublicNonABI linkage - // if the function was type-checked in Swift 4 mode. - if (kind == SILDeclRef::Kind::DefaultArgGenerator) { - if (isSerialized()) - return maybeAddExternal(SILLinkage::PublicNonABI); + const auto limit = getLinkageLimit(*this); + if (limit == Limit::Private) + return privateLinkage(); + + auto *decl = getDecl(); + + if (isPropertyWrapperBackingInitializer()) { + auto *dc = decl->getDeclContext(); + + // Property wrapper generators of public functions have PublicNonABI + // linkage. + if (isa(decl) && isSerialized()) + return SILLinkage::PublicNonABI; + + // Property wrappers in types have linkage based on the access level of + // their nominal. + if (dc->isTypeContext()) + decl = cast(dc); } - // Back deployment thunks and fallbacks are emitted into the client and - // therefore have PublicNonABI linkage. - if (backDeploymentKind != SILDeclRef::BackDeploymentKind::None) - return maybeAddExternal(SILLinkage::PublicNonABI); + // Stored property initializers have linkage based on the access level of + // their nominal. + if (isStoredPropertyInitializer()) + decl = cast(decl->getDeclContext()); - enum class Limit { - /// No limit. - 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; + // Compute the effective access level, taking e.g testable into consideration. + auto effectiveAccess = decl->getEffectiveAccess(); - // @_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()) - limit = Limit::AlwaysEmitIntoClient; - if (auto accessor = dyn_cast(d)) { - auto *storage = accessor->getStorage(); - if (storage->getAttrs().hasAttribute()) - 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(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(d)->isResilient()) { - limit = Limit::NeverPublic; - } - } - - if (auto fn = dyn_cast(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(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()) { - 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 // be at least internal as well, so that a dynamically-writable // keypath can be formed from other files in the same module. - if (auto accessor = dyn_cast(d)) { - if (accessor->isSetter() - && accessor->getStorage()->getEffectiveAccess() >= AccessLevel::Internal) + if (auto *accessor = dyn_cast(decl)) { + auto storageAccess = accessor->getStorage()->getEffectiveAccess(); + if (accessor->isSetter() && storageAccess >= AccessLevel::Internal) effectiveAccess = std::max(effectiveAccess, AccessLevel::Internal); } switch (effectiveAccess) { case AccessLevel::Private: case AccessLevel::FilePrivate: - return SILLinkage::Private; + return privateLinkage(); 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) return SILLinkage::Shared; - return maybeAddExternal(SILLinkage::Hidden); + return SILLinkage::Hidden; case AccessLevel::Public: 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; - if (limit == Limit::NeverPublic) - return maybeAddExternal(SILLinkage::Hidden); - if (limit == Limit::AlwaysEmitIntoClient) - return maybeAddExternal(SILLinkage::PublicNonABI); - return maybeAddExternal(SILLinkage::Public); + case Limit::NeverPublic: + return SILLinkage::Hidden; + case Limit::Private: + llvm_unreachable("Already handled"); + } } 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, unsigned defaultArgIndex) { SILDeclRef result; diff --git a/test/SILGen/always_emit_into_client_attribute.swift b/test/SILGen/always_emit_into_client_attribute.swift index 5a47317f752..3432756149b 100644 --- a/test/SILGen/always_emit_into_client_attribute.swift +++ b/test/SILGen/always_emit_into_client_attribute.swift @@ -29,3 +29,21 @@ public struct S { 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 {} +}