Skip to content

AST: Platform-specific fixes for -unavailable-decl-optimization #79248

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
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
27 changes: 25 additions & 2 deletions include/swift/AST/AvailabilityDomain.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "swift/AST/ASTAllocated.h"
#include "swift/AST/Identifier.h"
#include "swift/AST/PlatformKind.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/LLVM.h"
#include "llvm/ADT/FoldingSet.h"
#include "llvm/ADT/PointerEmbeddedInt.h"
Expand Down Expand Up @@ -102,7 +103,7 @@ class AvailabilityDomain final {

AvailabilityDomain(Kind kind)
: storage(InlineDomain(kind, PlatformKind::none).asInteger()) {
assert(kind != Kind::Platform);
DEBUG_ASSERT(kind != Kind::Platform);
};

AvailabilityDomain(PlatformKind platform)
Expand All @@ -120,7 +121,7 @@ class AvailabilityDomain final {
}

CustomAvailabilityDomain *getCustomDomain() const {
assert(isCustom());
ASSERT(isCustom());
return storage.get<CustomAvailabilityDomain *>();
}

Expand All @@ -132,6 +133,8 @@ class AvailabilityDomain final {
}

static AvailabilityDomain forPlatform(PlatformKind platformKind) {
bool isPlatform = platformKind != PlatformKind::none;
ASSERT(isPlatform);
return AvailabilityDomain(platformKind);
}

Expand All @@ -151,6 +154,16 @@ class AvailabilityDomain final {
return AvailabilityDomain(domain);
}

/// Returns the most specific platform domain for the target of the
/// compilation context.
static std::optional<AvailabilityDomain>
forTargetPlatform(const ASTContext &ctx);

/// Returns the most specific platform domain for the target variant of the
/// compilation context.
static std::optional<AvailabilityDomain>
forTargetVariantPlatform(const ASTContext &ctx);

/// Returns the built-in availability domain identified by the given string.
static std::optional<AvailabilityDomain>
builtinDomainForString(StringRef string, const DeclContext *declContext);
Expand Down Expand Up @@ -213,6 +226,11 @@ class AvailabilityDomain final {
/// universal domain (`*`) is the bottom element.
bool contains(const AvailabilityDomain &other) const;

/// Returns the root availability domain that this domain must be compatible
/// with. For example, macCatalyst and visionOS must both be ABI compatible
/// with iOS. The compatible domain must contain this domain.
AvailabilityDomain getABICompatibilityDomain() const;

bool operator==(const AvailabilityDomain &other) const {
return storage.getOpaqueValue() == other.storage.getOpaqueValue();
}
Expand All @@ -221,6 +239,11 @@ class AvailabilityDomain final {
return !(*this == other);
}

friend bool operator<(const AvailabilityDomain &lhs,
const AvailabilityDomain &rhs) {
return lhs.storage.getOpaqueValue() < rhs.storage.getOpaqueValue();
}

void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddPointer(getOpaqueValue());
}
Expand Down
7 changes: 3 additions & 4 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1432,8 +1432,8 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl>, public Swi
/// Returns the active platform-specific `@available` attribute for this decl.
/// There may be multiple `@available` attributes that are relevant to the
/// current platform, but the returned one has the highest priority.
std::optional<SemanticAvailableAttr> getActiveAvailableAttrForCurrentPlatform(
bool ignoreAppExtensions = false) const;
std::optional<SemanticAvailableAttr>
getActiveAvailableAttrForCurrentPlatform() const;

/// Returns the active platform-specific `@available` attribute that should be
/// used to determine the platform introduction version of the decl.
Expand Down Expand Up @@ -1479,8 +1479,7 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl>, public Swi
/// If the decl is always unavailable in the current compilation
/// context, returns the attribute attached to the decl (or its parent
/// extension) that makes it unavailable.
std::optional<SemanticAvailableAttr>
getUnavailableAttr(bool ignoreAppExtensions = false) const;
std::optional<SemanticAvailableAttr> getUnavailableAttr() const;

/// Returns true if the decl is effectively always unavailable in the current
/// compilation context. This query differs from \c isUnavailable() because it
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/PlatformKind.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ bool isPlatformActive(PlatformKind Platform, const LangOptions &LangOpts,
/// Returns the target platform for the given language options.
PlatformKind targetPlatform(const LangOptions &LangOpts);

/// Returns the target variant platform for the given language options.
PlatformKind targetVariantPlatform(const LangOptions &LangOpts);

/// Returns true when availability attributes from the "parent" platform
/// should also apply to the "child" platform for declarations without
/// an explicit attribute for the child.
Expand Down
126 changes: 81 additions & 45 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

#include "swift/AST/ASTContext.h"
#include "swift/AST/Attr.h"
#include "swift/AST/AvailabilityConstraint.h"
#include "swift/AST/AvailabilityContext.h"
#include "swift/AST/AvailabilityDomain.h"
#include "swift/AST/AvailabilityInference.h"
#include "swift/AST/AvailabilityRange.h"
Expand Down Expand Up @@ -454,17 +456,13 @@ Decl::getSemanticAvailableAttr(const AvailableAttr *attr) const {
}

std::optional<SemanticAvailableAttr>
Decl::getActiveAvailableAttrForCurrentPlatform(bool ignoreAppExtensions) const {
Decl::getActiveAvailableAttrForCurrentPlatform() const {
std::optional<SemanticAvailableAttr> bestAttr;

for (auto attr : getSemanticAvailableAttrs(/*includingInactive=*/false)) {
if (!attr.isPlatformSpecific())
continue;

if (ignoreAppExtensions &&
isApplicationExtensionPlatform(attr.getPlatform()))
continue;

// We have an attribute that is active for the platform, but is it more
// specific than our current best?
if (!bestAttr || inheritsAvailabilityFromPlatform(
Expand Down Expand Up @@ -575,23 +573,17 @@ bool Decl::isUnavailableInCurrentSwiftVersion() const {
return false;
}

std::optional<SemanticAvailableAttr>
getDeclUnavailableAttr(const Decl *D, bool ignoreAppExtensions) {
std::optional<SemanticAvailableAttr> getDeclUnavailableAttr(const Decl *D) {
auto &ctx = D->getASTContext();
std::optional<SemanticAvailableAttr> result;
auto bestActive =
D->getActiveAvailableAttrForCurrentPlatform(ignoreAppExtensions);
auto bestActive = D->getActiveAvailableAttrForCurrentPlatform();

for (auto attr : D->getSemanticAvailableAttrs(/*includingInactive=*/false)) {
// If this is a platform-specific attribute and it isn't the most
// specific attribute for the current platform, we're done.
if (attr.isPlatformSpecific() && (!bestActive || attr != bestActive))
continue;

if (ignoreAppExtensions &&
isApplicationExtensionPlatform(attr.getPlatform()))
continue;

// Unconditional unavailable.
if (attr.isUnconditionallyUnavailable())
return attr;
Expand All @@ -610,9 +602,8 @@ getDeclUnavailableAttr(const Decl *D, bool ignoreAppExtensions) {
return result;
}

std::optional<SemanticAvailableAttr>
Decl::getUnavailableAttr(bool ignoreAppExtensions) const {
if (auto attr = getDeclUnavailableAttr(this, ignoreAppExtensions))
std::optional<SemanticAvailableAttr> Decl::getUnavailableAttr() const {
if (auto attr = getDeclUnavailableAttr(this))
return attr;

// If D is an extension member, check if the extension is unavailable.
Expand All @@ -624,36 +615,89 @@ Decl::getUnavailableAttr(bool ignoreAppExtensions) const {
// and the availability of other categories. rdar://problem/53956555
if (!getClangNode())
if (auto ext = dyn_cast<ExtensionDecl>(getDeclContext()))
return ext->getUnavailableAttr(ignoreAppExtensions);
return ext->getUnavailableAttr();

return std::nullopt;
}

static bool isDeclCompletelyUnavailable(const Decl *decl) {
// Don't trust unavailability on declarations from clang modules.
static llvm::SmallVector<AvailabilityDomain, 2>
availabilityDomainsForABICompatibility(const ASTContext &ctx) {
llvm::SmallVector<AvailabilityDomain, 2> domains;

// Regardless of target platform, binaries built for Embedded do not require
// compatibility.
if (ctx.LangOpts.hasFeature(Feature::Embedded))
return domains;

if (auto targetDomain = AvailabilityDomain::forTargetPlatform(ctx))
domains.push_back(targetDomain->getABICompatibilityDomain());

if (auto variantDomain = AvailabilityDomain::forTargetVariantPlatform(ctx))
domains.push_back(variantDomain->getABICompatibilityDomain());

return domains;
}

/// Returns true if \p decl is proven to be unavailable for all platforms that
/// external modules interacting with this module could target. A declaration
/// that is not proven to be unavailable in this way could be reachable at
/// runtime, even if it is unavailable to all code in this module.
static bool isUnavailableForAllABICompatiblePlatforms(const Decl *decl) {
// Don't trust unavailability on declarations from Clang modules.
if (isa<ClangModuleUnit>(decl->getDeclContext()->getModuleScopeContext()))
return false;

auto unavailableAttr = decl->getUnavailableAttr(/*ignoreAppExtensions=*/true);
if (!unavailableAttr)
return false;
auto &ctx = decl->getASTContext();
llvm::SmallVector<AvailabilityDomain, 2> compatibilityDomains =
availabilityDomainsForABICompatibility(ctx);

llvm::SmallSet<AvailabilityDomain, 8> unavailableDescendantDomains;
llvm::SmallSet<AvailabilityDomain, 8> availableDescendantDomains;

// Build up the collection of relevant available and unavailable platform
// domains by looking at all the @available attributes. Along the way, we
// may find an attribute that makes the declaration universally unavailable
// in which case platform availability is irrelevant.
for (auto attr : decl->getSemanticAvailableAttrs(/*includeInactive=*/true)) {
auto domain = attr.getDomain();
bool isCompabilityDomainDescendant =
llvm::find_if(compatibilityDomains,
[&domain](AvailabilityDomain compatibilityDomain) {
return compatibilityDomain.contains(domain);
}) != compatibilityDomains.end();

if (isCompabilityDomainDescendant) {
// Record the whether the descendant domain is marked available
// or unavailable. Unavailability overrides availability.
if (attr.isUnconditionallyUnavailable()) {
availableDescendantDomains.erase(domain);
unavailableDescendantDomains.insert(domain);
} else if (!unavailableDescendantDomains.contains(domain)) {
availableDescendantDomains.insert(domain);
}
} else if (attr.isActive(ctx)) {
// The declaration is always unavailable if an active attribute from a
// domain outside the compatibility hierarchy indicates unavailability.
if (attr.isUnconditionallyUnavailable())
return true;
}
}

// getUnavailableAttr() can return an @available attribute that is
// obsoleted for certain deployment targets or language modes. These decls
// can still be reached by code in other modules that is compiled with
// a different deployment target or language mode.
if (!unavailableAttr->isUnconditionallyUnavailable())
// If there aren't any compatibility domains to check and we didn't find any
// other active attributes that make the declaration unavailable, then it must
// be available.
if (compatibilityDomains.empty())
return false;

// Universally unavailable declarations are always completely unavailable.
if (unavailableAttr->getPlatform() == PlatformKind::none)
return true;

// FIXME: Support zippered frameworks (rdar://125371621)
// If we have a target variant (e.g. we're building a zippered macOS
// framework) then the decl is only unreachable if it is unavailable for both
// the primary target and the target variant.
if (decl->getASTContext().LangOpts.TargetVariant.has_value())
// Verify that the declaration has been marked unavailable in every
// compatibility domain.
for (auto compatibilityDomain : compatibilityDomains) {
if (!unavailableDescendantDomains.contains(compatibilityDomain))
return false;
}

// Verify that there aren't any explicitly available descendant domains.
if (availableDescendantDomains.size() > 0)
return false;

return true;
Expand All @@ -670,7 +714,7 @@ SemanticDeclAvailabilityRequest::evaluate(Evaluator &evaluator,
}

if (inherited == SemanticDeclAvailability::CompletelyUnavailable ||
isDeclCompletelyUnavailable(decl))
isUnavailableForAllABICompatiblePlatforms(decl))
return SemanticDeclAvailability::CompletelyUnavailable;

if (inherited == SemanticDeclAvailability::ConditionallyUnavailable ||
Expand Down Expand Up @@ -699,14 +743,6 @@ getEffectiveUnavailableDeclOptimization(ASTContext &ctx) {
if (ctx.LangOpts.UnavailableDeclOptimizationMode.has_value())
return *ctx.LangOpts.UnavailableDeclOptimizationMode;

// FIXME: Allow unavailable decl optimization on visionOS.
// visionOS must be ABI compatible with iOS. Enabling unavailable declaration
// optimizations naively would break compatibility since declarations marked
// unavailable on visionOS would be optimized regardless of whether they are
// available on iOS. rdar://116742214
if (ctx.LangOpts.Target.isXROS())
return UnavailableDeclOptimization::None;

return UnavailableDeclOptimization::None;
}

Expand Down
32 changes: 32 additions & 0 deletions lib/AST/AvailabilityDomain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@

using namespace swift;

std::optional<AvailabilityDomain>
AvailabilityDomain::forTargetPlatform(const ASTContext &ctx) {
auto platform = swift::targetPlatform(ctx.LangOpts);
if (platform == PlatformKind::none)
return std::nullopt;

return forPlatform(platform);
}

std::optional<AvailabilityDomain>
AvailabilityDomain::forTargetVariantPlatform(const ASTContext &ctx) {
auto platform = swift::targetVariantPlatform(ctx.LangOpts);
if (platform == PlatformKind::none)
return std::nullopt;

return forPlatform(platform);
}

std::optional<AvailabilityDomain>
AvailabilityDomain::builtinDomainForString(StringRef string,
const DeclContext *declContext) {
Expand Down Expand Up @@ -136,6 +154,20 @@ bool AvailabilityDomain::contains(const AvailabilityDomain &other) const {
}
}

AvailabilityDomain AvailabilityDomain::getABICompatibilityDomain() const {
if (!isPlatform())
return *this;

auto iOSDomain = AvailabilityDomain::forPlatform(PlatformKind::iOS);
if (iOSDomain.contains(*this))
return iOSDomain;

if (auto basePlatform = basePlatformForExtensionPlatform(getPlatformKind()))
return AvailabilityDomain::forPlatform(*basePlatform);

return *this;
}

CustomAvailabilityDomain::CustomAvailabilityDomain(Identifier name,
ModuleDecl *mod, Kind kind)
: name(name), kind(kind), mod(mod) {
Expand Down
Loading