Skip to content

Commit 0225964

Browse files
authored
Merge pull request #79248 from tshortli/abi-compatible-platform-decl-reachability
AST: Platform-specific fixes for `-unavailable-decl-optimization`
2 parents 31af4d9 + 670bfc0 commit 0225964

15 files changed

+324
-106
lines changed

include/swift/AST/AvailabilityDomain.h

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "swift/AST/ASTAllocated.h"
2222
#include "swift/AST/Identifier.h"
2323
#include "swift/AST/PlatformKind.h"
24+
#include "swift/Basic/Assertions.h"
2425
#include "swift/Basic/LLVM.h"
2526
#include "llvm/ADT/FoldingSet.h"
2627
#include "llvm/ADT/PointerEmbeddedInt.h"
@@ -102,7 +103,7 @@ class AvailabilityDomain final {
102103

103104
AvailabilityDomain(Kind kind)
104105
: storage(InlineDomain(kind, PlatformKind::none).asInteger()) {
105-
assert(kind != Kind::Platform);
106+
DEBUG_ASSERT(kind != Kind::Platform);
106107
};
107108

108109
AvailabilityDomain(PlatformKind platform)
@@ -120,7 +121,7 @@ class AvailabilityDomain final {
120121
}
121122

122123
CustomAvailabilityDomain *getCustomDomain() const {
123-
assert(isCustom());
124+
ASSERT(isCustom());
124125
return storage.get<CustomAvailabilityDomain *>();
125126
}
126127

@@ -132,6 +133,8 @@ class AvailabilityDomain final {
132133
}
133134

134135
static AvailabilityDomain forPlatform(PlatformKind platformKind) {
136+
bool isPlatform = platformKind != PlatformKind::none;
137+
ASSERT(isPlatform);
135138
return AvailabilityDomain(platformKind);
136139
}
137140

@@ -151,6 +154,16 @@ class AvailabilityDomain final {
151154
return AvailabilityDomain(domain);
152155
}
153156

157+
/// Returns the most specific platform domain for the target of the
158+
/// compilation context.
159+
static std::optional<AvailabilityDomain>
160+
forTargetPlatform(const ASTContext &ctx);
161+
162+
/// Returns the most specific platform domain for the target variant of the
163+
/// compilation context.
164+
static std::optional<AvailabilityDomain>
165+
forTargetVariantPlatform(const ASTContext &ctx);
166+
154167
/// Returns the built-in availability domain identified by the given string.
155168
static std::optional<AvailabilityDomain>
156169
builtinDomainForString(StringRef string, const DeclContext *declContext);
@@ -213,6 +226,11 @@ class AvailabilityDomain final {
213226
/// universal domain (`*`) is the bottom element.
214227
bool contains(const AvailabilityDomain &other) const;
215228

229+
/// Returns the root availability domain that this domain must be compatible
230+
/// with. For example, macCatalyst and visionOS must both be ABI compatible
231+
/// with iOS. The compatible domain must contain this domain.
232+
AvailabilityDomain getABICompatibilityDomain() const;
233+
216234
bool operator==(const AvailabilityDomain &other) const {
217235
return storage.getOpaqueValue() == other.storage.getOpaqueValue();
218236
}
@@ -221,6 +239,11 @@ class AvailabilityDomain final {
221239
return !(*this == other);
222240
}
223241

242+
friend bool operator<(const AvailabilityDomain &lhs,
243+
const AvailabilityDomain &rhs) {
244+
return lhs.storage.getOpaqueValue() < rhs.storage.getOpaqueValue();
245+
}
246+
224247
void Profile(llvm::FoldingSetNodeID &ID) const {
225248
ID.AddPointer(getOpaqueValue());
226249
}

include/swift/AST/Decl.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,8 +1432,8 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl>, public Swi
14321432
/// Returns the active platform-specific `@available` attribute for this decl.
14331433
/// There may be multiple `@available` attributes that are relevant to the
14341434
/// current platform, but the returned one has the highest priority.
1435-
std::optional<SemanticAvailableAttr> getActiveAvailableAttrForCurrentPlatform(
1436-
bool ignoreAppExtensions = false) const;
1435+
std::optional<SemanticAvailableAttr>
1436+
getActiveAvailableAttrForCurrentPlatform() const;
14371437

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

14851484
/// Returns true if the decl is effectively always unavailable in the current
14861485
/// compilation context. This query differs from \c isUnavailable() because it

include/swift/AST/PlatformKind.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ bool isPlatformActive(PlatformKind Platform, const LangOptions &LangOpts,
8383
/// Returns the target platform for the given language options.
8484
PlatformKind targetPlatform(const LangOptions &LangOpts);
8585

86+
/// Returns the target variant platform for the given language options.
87+
PlatformKind targetVariantPlatform(const LangOptions &LangOpts);
88+
8689
/// Returns true when availability attributes from the "parent" platform
8790
/// should also apply to the "child" platform for declarations without
8891
/// an explicit attribute for the child.

lib/AST/Availability.cpp

Lines changed: 81 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
#include "swift/AST/ASTContext.h"
1818
#include "swift/AST/Attr.h"
19+
#include "swift/AST/AvailabilityConstraint.h"
20+
#include "swift/AST/AvailabilityContext.h"
1921
#include "swift/AST/AvailabilityDomain.h"
2022
#include "swift/AST/AvailabilityInference.h"
2123
#include "swift/AST/AvailabilityRange.h"
@@ -454,17 +456,13 @@ Decl::getSemanticAvailableAttr(const AvailableAttr *attr) const {
454456
}
455457

456458
std::optional<SemanticAvailableAttr>
457-
Decl::getActiveAvailableAttrForCurrentPlatform(bool ignoreAppExtensions) const {
459+
Decl::getActiveAvailableAttrForCurrentPlatform() const {
458460
std::optional<SemanticAvailableAttr> bestAttr;
459461

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

464-
if (ignoreAppExtensions &&
465-
isApplicationExtensionPlatform(attr.getPlatform()))
466-
continue;
467-
468466
// We have an attribute that is active for the platform, but is it more
469467
// specific than our current best?
470468
if (!bestAttr || inheritsAvailabilityFromPlatform(
@@ -575,23 +573,17 @@ bool Decl::isUnavailableInCurrentSwiftVersion() const {
575573
return false;
576574
}
577575

578-
std::optional<SemanticAvailableAttr>
579-
getDeclUnavailableAttr(const Decl *D, bool ignoreAppExtensions) {
576+
std::optional<SemanticAvailableAttr> getDeclUnavailableAttr(const Decl *D) {
580577
auto &ctx = D->getASTContext();
581578
std::optional<SemanticAvailableAttr> result;
582-
auto bestActive =
583-
D->getActiveAvailableAttrForCurrentPlatform(ignoreAppExtensions);
579+
auto bestActive = D->getActiveAvailableAttrForCurrentPlatform();
584580

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

591-
if (ignoreAppExtensions &&
592-
isApplicationExtensionPlatform(attr.getPlatform()))
593-
continue;
594-
595587
// Unconditional unavailable.
596588
if (attr.isUnconditionallyUnavailable())
597589
return attr;
@@ -610,9 +602,8 @@ getDeclUnavailableAttr(const Decl *D, bool ignoreAppExtensions) {
610602
return result;
611603
}
612604

613-
std::optional<SemanticAvailableAttr>
614-
Decl::getUnavailableAttr(bool ignoreAppExtensions) const {
615-
if (auto attr = getDeclUnavailableAttr(this, ignoreAppExtensions))
605+
std::optional<SemanticAvailableAttr> Decl::getUnavailableAttr() const {
606+
if (auto attr = getDeclUnavailableAttr(this))
616607
return attr;
617608

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

629620
return std::nullopt;
630621
}
631622

632-
static bool isDeclCompletelyUnavailable(const Decl *decl) {
633-
// Don't trust unavailability on declarations from clang modules.
623+
static llvm::SmallVector<AvailabilityDomain, 2>
624+
availabilityDomainsForABICompatibility(const ASTContext &ctx) {
625+
llvm::SmallVector<AvailabilityDomain, 2> domains;
626+
627+
// Regardless of target platform, binaries built for Embedded do not require
628+
// compatibility.
629+
if (ctx.LangOpts.hasFeature(Feature::Embedded))
630+
return domains;
631+
632+
if (auto targetDomain = AvailabilityDomain::forTargetPlatform(ctx))
633+
domains.push_back(targetDomain->getABICompatibilityDomain());
634+
635+
if (auto variantDomain = AvailabilityDomain::forTargetVariantPlatform(ctx))
636+
domains.push_back(variantDomain->getABICompatibilityDomain());
637+
638+
return domains;
639+
}
640+
641+
/// Returns true if \p decl is proven to be unavailable for all platforms that
642+
/// external modules interacting with this module could target. A declaration
643+
/// that is not proven to be unavailable in this way could be reachable at
644+
/// runtime, even if it is unavailable to all code in this module.
645+
static bool isUnavailableForAllABICompatiblePlatforms(const Decl *decl) {
646+
// Don't trust unavailability on declarations from Clang modules.
634647
if (isa<ClangModuleUnit>(decl->getDeclContext()->getModuleScopeContext()))
635648
return false;
636649

637-
auto unavailableAttr = decl->getUnavailableAttr(/*ignoreAppExtensions=*/true);
638-
if (!unavailableAttr)
639-
return false;
650+
auto &ctx = decl->getASTContext();
651+
llvm::SmallVector<AvailabilityDomain, 2> compatibilityDomains =
652+
availabilityDomainsForABICompatibility(ctx);
653+
654+
llvm::SmallSet<AvailabilityDomain, 8> unavailableDescendantDomains;
655+
llvm::SmallSet<AvailabilityDomain, 8> availableDescendantDomains;
656+
657+
// Build up the collection of relevant available and unavailable platform
658+
// domains by looking at all the @available attributes. Along the way, we
659+
// may find an attribute that makes the declaration universally unavailable
660+
// in which case platform availability is irrelevant.
661+
for (auto attr : decl->getSemanticAvailableAttrs(/*includeInactive=*/true)) {
662+
auto domain = attr.getDomain();
663+
bool isCompabilityDomainDescendant =
664+
llvm::find_if(compatibilityDomains,
665+
[&domain](AvailabilityDomain compatibilityDomain) {
666+
return compatibilityDomain.contains(domain);
667+
}) != compatibilityDomains.end();
668+
669+
if (isCompabilityDomainDescendant) {
670+
// Record the whether the descendant domain is marked available
671+
// or unavailable. Unavailability overrides availability.
672+
if (attr.isUnconditionallyUnavailable()) {
673+
availableDescendantDomains.erase(domain);
674+
unavailableDescendantDomains.insert(domain);
675+
} else if (!unavailableDescendantDomains.contains(domain)) {
676+
availableDescendantDomains.insert(domain);
677+
}
678+
} else if (attr.isActive(ctx)) {
679+
// The declaration is always unavailable if an active attribute from a
680+
// domain outside the compatibility hierarchy indicates unavailability.
681+
if (attr.isUnconditionallyUnavailable())
682+
return true;
683+
}
684+
}
640685

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

648-
// Universally unavailable declarations are always completely unavailable.
649-
if (unavailableAttr->getPlatform() == PlatformKind::none)
650-
return true;
651-
652-
// FIXME: Support zippered frameworks (rdar://125371621)
653-
// If we have a target variant (e.g. we're building a zippered macOS
654-
// framework) then the decl is only unreachable if it is unavailable for both
655-
// the primary target and the target variant.
656-
if (decl->getASTContext().LangOpts.TargetVariant.has_value())
692+
// Verify that the declaration has been marked unavailable in every
693+
// compatibility domain.
694+
for (auto compatibilityDomain : compatibilityDomains) {
695+
if (!unavailableDescendantDomains.contains(compatibilityDomain))
696+
return false;
697+
}
698+
699+
// Verify that there aren't any explicitly available descendant domains.
700+
if (availableDescendantDomains.size() > 0)
657701
return false;
658702

659703
return true;
@@ -670,7 +714,7 @@ SemanticDeclAvailabilityRequest::evaluate(Evaluator &evaluator,
670714
}
671715

672716
if (inherited == SemanticDeclAvailability::CompletelyUnavailable ||
673-
isDeclCompletelyUnavailable(decl))
717+
isUnavailableForAllABICompatiblePlatforms(decl))
674718
return SemanticDeclAvailability::CompletelyUnavailable;
675719

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

702-
// FIXME: Allow unavailable decl optimization on visionOS.
703-
// visionOS must be ABI compatible with iOS. Enabling unavailable declaration
704-
// optimizations naively would break compatibility since declarations marked
705-
// unavailable on visionOS would be optimized regardless of whether they are
706-
// available on iOS. rdar://116742214
707-
if (ctx.LangOpts.Target.isXROS())
708-
return UnavailableDeclOptimization::None;
709-
710746
return UnavailableDeclOptimization::None;
711747
}
712748

lib/AST/AvailabilityDomain.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,24 @@
1818

1919
using namespace swift;
2020

21+
std::optional<AvailabilityDomain>
22+
AvailabilityDomain::forTargetPlatform(const ASTContext &ctx) {
23+
auto platform = swift::targetPlatform(ctx.LangOpts);
24+
if (platform == PlatformKind::none)
25+
return std::nullopt;
26+
27+
return forPlatform(platform);
28+
}
29+
30+
std::optional<AvailabilityDomain>
31+
AvailabilityDomain::forTargetVariantPlatform(const ASTContext &ctx) {
32+
auto platform = swift::targetVariantPlatform(ctx.LangOpts);
33+
if (platform == PlatformKind::none)
34+
return std::nullopt;
35+
36+
return forPlatform(platform);
37+
}
38+
2139
std::optional<AvailabilityDomain>
2240
AvailabilityDomain::builtinDomainForString(StringRef string,
2341
const DeclContext *declContext) {
@@ -136,6 +154,20 @@ bool AvailabilityDomain::contains(const AvailabilityDomain &other) const {
136154
}
137155
}
138156

157+
AvailabilityDomain AvailabilityDomain::getABICompatibilityDomain() const {
158+
if (!isPlatform())
159+
return *this;
160+
161+
auto iOSDomain = AvailabilityDomain::forPlatform(PlatformKind::iOS);
162+
if (iOSDomain.contains(*this))
163+
return iOSDomain;
164+
165+
if (auto basePlatform = basePlatformForExtensionPlatform(getPlatformKind()))
166+
return AvailabilityDomain::forPlatform(*basePlatform);
167+
168+
return *this;
169+
}
170+
139171
CustomAvailabilityDomain::CustomAvailabilityDomain(Identifier name,
140172
ModuleDecl *mod, Kind kind)
141173
: name(name), kind(kind), mod(mod) {

0 commit comments

Comments
 (0)