Skip to content

Commit 2309793

Browse files
committed
AST: Refactor SemanticDeclAvailabilityRequest.
Generalize the implementation of `SemanticDeclAvailabilityRequest` in preparation for adding a new case to `SemanticDeclAvailability`. Use the centralized availability constraint query instead of implementing a bespoke algorithm for gathering constraints. Simplify `SemanticDeclAvailability` by removing a case that is no longer relevant. Part of rdar://138441307.
1 parent 398b0de commit 2309793

File tree

6 files changed

+153
-90
lines changed

6 files changed

+153
-90
lines changed

include/swift/AST/AvailabilityConstraint.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ enum class AvailabilityConstraintFlag : uint8_t {
170170
/// referencing the extension. When this flag is specified, though, only the
171171
/// attributes directly attached to the declaration are considered.
172172
SkipEnclosingExtension = 1 << 0,
173+
174+
/// Include constraints for all domains, regardless of whether they are active
175+
/// or relevant to type checking.
176+
IncludeAllDomains = 1 << 1,
173177
};
174178
using AvailabilityConstraintFlags = OptionSet<AvailabilityConstraintFlag>;
175179

include/swift/AST/TypeCheckRequests.h

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4128,21 +4128,31 @@ class RenamedDeclRequest
41284128
void cacheResult(ValueDecl *value) const;
41294129
};
41304130

4131+
/// Describes the runtime availability of a declaration, which is a
4132+
/// classification of whether a decl can be used at runtime (as opposed to
4133+
/// compile time).
4134+
///
4135+
/// The elements of this enumeration must be ordered from most available to
4136+
/// least available.
41314137
enum class SemanticDeclAvailability : uint8_t {
4132-
/// The decl is potentially available in some contexts and/or under certain
4133-
/// deployment conditions.
4138+
/// The decl is potentially available at runtime. If it is unavailable at
4139+
/// compile time in the current module, it may still be considered available
4140+
/// at compile time by other modules with different settings. For example, a
4141+
/// decl that is obsolete in Swift 5 is still available to other modules that
4142+
/// are compiled for an earlier language mode.
41344143
PotentiallyAvailable,
41354144

4136-
/// The decl is always unavailable in the current compilation context.
4137-
/// However, it may still be used at runtime by other modules with different
4138-
/// settings. For example a decl that is obsolete in Swift 5 is still
4139-
/// available to other modules compiled for an earlier language mode.
4140-
ConditionallyUnavailable,
4141-
4142-
/// The decl is universally unavailable. For example, when compiling for macOS
4143-
/// a decl with `@available(macOS, unavailable)` can never be used (except in
4144-
/// contexts that are also completely unavailable on macOS).
4145-
CompletelyUnavailable,
4145+
/// The decl is always unavailable at compile time in the current module and
4146+
/// all other modules, but it is still required to be present at load time to
4147+
/// maintain ABI compatibility. For example, when compiling for macOS a decl
4148+
/// with an `@available(macOS, unavailable)` attribute can never be invoked,
4149+
/// except in contexts that are also completely unavailable on macOS. This
4150+
/// means the declaration is unreachable by execution at runtime, but the
4151+
/// decl's symbols may still have been strongly linked by other binaries built
4152+
/// by older versions of the compiler which may have emitted unavailable code
4153+
/// with strong references. To preserve ABI stability, the decl must still be
4154+
/// emitted.
4155+
AlwaysUnavailableABICompatible,
41464156
};
41474157

41484158
class SemanticDeclAvailabilityRequest

lib/AST/Availability.cpp

Lines changed: 67 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -541,87 +541,93 @@ std::optional<SemanticAvailableAttr> Decl::getUnavailableAttr() const {
541541
return std::nullopt;
542542
}
543543

544-
static llvm::SmallVector<AvailabilityDomain, 2>
544+
static llvm::SmallSetVector<AvailabilityDomain, 2>
545545
availabilityDomainsForABICompatibility(const ASTContext &ctx) {
546-
llvm::SmallVector<AvailabilityDomain, 2> domains;
546+
llvm::SmallSetVector<AvailabilityDomain, 2> domains;
547547

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

553553
if (auto targetDomain = AvailabilityDomain::forTargetPlatform(ctx))
554-
domains.push_back(targetDomain->getABICompatibilityDomain());
555-
554+
domains.insert(targetDomain->getABICompatibilityDomain());
555+
556556
if (auto variantDomain = AvailabilityDomain::forTargetVariantPlatform(ctx))
557-
domains.push_back(variantDomain->getABICompatibilityDomain());
557+
domains.insert(variantDomain->getABICompatibilityDomain());
558558

559559
return domains;
560560
}
561561

562-
/// Returns true if \p decl is proven to be unavailable for all platforms that
563-
/// external modules interacting with this module could target. A declaration
564-
/// that is not proven to be unavailable in this way could be reachable at
565-
/// runtime, even if it is unavailable to all code in this module.
566-
static bool isUnavailableForAllABICompatiblePlatforms(const Decl *decl) {
562+
static bool constraintIndicatesRuntimeUnavailability(
563+
const AvailabilityConstraint &constraint, const ASTContext &ctx) {
564+
switch (constraint.getReason()) {
565+
case AvailabilityConstraint::Reason::UnconditionallyUnavailable: {
566+
return true;
567+
}
568+
case AvailabilityConstraint::Reason::UnavailableForDeployment:
569+
case AvailabilityConstraint::Reason::Obsoleted:
570+
case AvailabilityConstraint::Reason::PotentiallyUnavailable:
571+
return false;
572+
}
573+
}
574+
575+
/// Computes the `SemanticDeclAvailability` value for `decl`.
576+
static SemanticDeclAvailability getSemanticDeclAvailability(const Decl *decl) {
567577
// Don't trust unavailability on declarations from Clang modules.
568578
if (isa<ClangModuleUnit>(decl->getDeclContext()->getModuleScopeContext()))
569-
return false;
579+
return SemanticDeclAvailability::PotentiallyAvailable;
580+
581+
// Check whether the decl is unavailable at all.
582+
if (!decl->isUnavailable())
583+
return SemanticDeclAvailability::PotentiallyAvailable;
570584

571585
auto &ctx = decl->getASTContext();
572-
llvm::SmallVector<AvailabilityDomain, 2> compatibilityDomains =
573-
availabilityDomainsForABICompatibility(ctx);
574-
575-
llvm::SmallSet<AvailabilityDomain, 8> unavailableDescendantDomains;
576-
llvm::SmallSet<AvailabilityDomain, 8> availableDescendantDomains;
577-
578-
// Build up the collection of relevant available and unavailable platform
579-
// domains by looking at all the @available attributes. Along the way, we
580-
// may find an attribute that makes the declaration universally unavailable
581-
// in which case platform availability is irrelevant.
582-
for (auto attr : decl->getSemanticAvailableAttrs(/*includeInactive=*/true)) {
583-
auto domain = attr.getDomain();
586+
auto compatibilityDomains = availabilityDomainsForABICompatibility(ctx);
587+
auto potentiallyAvailableDomains = compatibilityDomains;
588+
589+
AvailabilityConstraintFlags flags;
590+
591+
// Semantic availability was already computed separately for any enclosing
592+
// extension.
593+
flags |= AvailabilityConstraintFlag::SkipEnclosingExtension;
594+
595+
// FIXME: [availability] Inactive domains have to be included because iOS
596+
// availability is considered inactive when compiling a zippered module.
597+
flags |= AvailabilityConstraintFlag::IncludeAllDomains;
598+
599+
auto constraints = getAvailabilityConstraintsForDecl(
600+
decl, AvailabilityContext::forInliningTarget(ctx), flags);
601+
602+
for (auto constraint : constraints) {
603+
if (!constraintIndicatesRuntimeUnavailability(constraint, ctx))
604+
continue;
605+
606+
// Check whether the constraint is from a relevant domain.
607+
auto domain = constraint.getDomain();
584608
bool isCompabilityDomainDescendant =
585609
llvm::find_if(compatibilityDomains,
586610
[&domain](AvailabilityDomain compatibilityDomain) {
587611
return compatibilityDomain.contains(domain);
588612
}) != compatibilityDomains.end();
589613

614+
if (!domain.isActive(ctx) && !isCompabilityDomainDescendant)
615+
continue;
616+
590617
if (isCompabilityDomainDescendant) {
591-
// Record the whether the descendant domain is marked available
592-
// or unavailable. Unavailability overrides availability.
593-
if (attr.isUnconditionallyUnavailable()) {
594-
availableDescendantDomains.erase(domain);
595-
unavailableDescendantDomains.insert(domain);
596-
} else if (!unavailableDescendantDomains.contains(domain)) {
597-
availableDescendantDomains.insert(domain);
598-
}
599-
} else if (attr.isActive(ctx)) {
600-
// The declaration is always unavailable if an active attribute from a
601-
// domain outside the compatibility hierarchy indicates unavailability.
602-
if (attr.isUnconditionallyUnavailable())
603-
return true;
618+
// If the decl is still potentially available in some compatibility
619+
// domain, keep looking at the remaining constraints.
620+
potentiallyAvailableDomains.remove(domain);
621+
if (!potentiallyAvailableDomains.empty())
622+
continue;
604623
}
605-
}
606-
607-
// If there aren't any compatibility domains to check and we didn't find any
608-
// other active attributes that make the declaration unavailable, then it must
609-
// be available.
610-
if (compatibilityDomains.empty())
611-
return false;
612624

613-
// Verify that the declaration has been marked unavailable in every
614-
// compatibility domain.
615-
for (auto compatibilityDomain : compatibilityDomains) {
616-
if (!unavailableDescendantDomains.contains(compatibilityDomain))
617-
return false;
625+
// Either every compatibility domain has been proven unavailable or this
626+
// constraint proves runtime unavailability on its own.
627+
return SemanticDeclAvailability::AlwaysUnavailableABICompatible;
618628
}
619-
620-
// Verify that there aren't any explicitly available descendant domains.
621-
if (availableDescendantDomains.size() > 0)
622-
return false;
623629

624-
return true;
630+
return SemanticDeclAvailability::PotentiallyAvailable;
625631
}
626632

627633
SemanticDeclAvailability
@@ -634,22 +640,21 @@ SemanticDeclAvailabilityRequest::evaluate(Evaluator &evaluator,
634640
evaluator, SemanticDeclAvailabilityRequest{parent}, inherited);
635641
}
636642

637-
if (inherited == SemanticDeclAvailability::CompletelyUnavailable ||
638-
isUnavailableForAllABICompatiblePlatforms(decl))
639-
return SemanticDeclAvailability::CompletelyUnavailable;
640-
641-
if (inherited == SemanticDeclAvailability::ConditionallyUnavailable ||
642-
decl->isUnavailable())
643-
return SemanticDeclAvailability::ConditionallyUnavailable;
643+
// If the inherited semantic availability is already maximally unavailable
644+
// then skip computing unavailability for this declaration.
645+
if (inherited == SemanticDeclAvailability::AlwaysUnavailableABICompatible)
646+
return SemanticDeclAvailability::AlwaysUnavailableABICompatible;
644647

645-
return SemanticDeclAvailability::PotentiallyAvailable;
648+
auto availability = getSemanticDeclAvailability(decl);
649+
return std::max(inherited, availability);
646650
}
647651

648652
bool Decl::isUnreachableAtRuntime() const {
649653
auto availability = evaluateOrDefault(
650654
getASTContext().evaluator, SemanticDeclAvailabilityRequest{this},
651655
SemanticDeclAvailability::PotentiallyAvailable);
652-
return availability == SemanticDeclAvailability::CompletelyUnavailable;
656+
return availability ==
657+
SemanticDeclAvailability::AlwaysUnavailableABICompatible;
653658
}
654659

655660
static UnavailableDeclOptimization
@@ -852,8 +857,6 @@ std::optional<llvm::VersionTuple> SemanticAvailableAttr::getIntroduced() const {
852857

853858
std::optional<AvailabilityRange>
854859
SemanticAvailableAttr::getIntroducedRange(const ASTContext &Ctx) const {
855-
DEBUG_ASSERT(getDomain().isActive(Ctx));
856-
857860
auto *attr = getParsedAttr();
858861
if (!attr->getRawIntroduced().has_value()) {
859862
// For versioned domains, an "introduced:" version is always required to
@@ -892,8 +895,6 @@ std::optional<llvm::VersionTuple> SemanticAvailableAttr::getDeprecated() const {
892895

893896
std::optional<AvailabilityRange>
894897
SemanticAvailableAttr::getDeprecatedRange(const ASTContext &Ctx) const {
895-
DEBUG_ASSERT(getDomain().isActive(Ctx));
896-
897898
auto *attr = getParsedAttr();
898899
if (!attr->getRawDeprecated().has_value()) {
899900
// Regardless of the whether the domain supports versions or not, an
@@ -923,8 +924,6 @@ std::optional<llvm::VersionTuple> SemanticAvailableAttr::getObsoleted() const {
923924

924925
std::optional<AvailabilityRange>
925926
SemanticAvailableAttr::getObsoletedRange(const ASTContext &Ctx) const {
926-
DEBUG_ASSERT(getDomain().isActive(Ctx));
927-
928927
auto *attr = getParsedAttr();
929928

930929
// Obsoletion always requires a version.

lib/AST/AvailabilityConstraint.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,15 @@ activePlatformDomainForDecl(const Decl *decl) {
198198

199199
static void getAvailabilityConstraintsForDecl(
200200
llvm::SmallVector<AvailabilityConstraint, 4> &constraints, const Decl *decl,
201-
const AvailabilityContext &context) {
201+
const AvailabilityContext &context, AvailabilityConstraintFlags flags) {
202202
auto &ctx = decl->getASTContext();
203203
auto activePlatformDomain = activePlatformDomainForDecl(decl);
204+
bool includeAllDomains =
205+
flags.contains(AvailabilityConstraintFlag::IncludeAllDomains);
204206

205-
for (auto attr :
206-
decl->getSemanticAvailableAttrs(/*includingInactive=*/false)) {
207+
for (auto attr : decl->getSemanticAvailableAttrs(includeAllDomains)) {
207208
auto domain = attr.getDomain();
208-
if (domain.isPlatform() && activePlatformDomain &&
209+
if (!includeAllDomains && domain.isPlatform() && activePlatformDomain &&
209210
!activePlatformDomain->contains(domain))
210211
continue;
211212

@@ -234,7 +235,7 @@ swift::getAvailabilityConstraintsForDecl(const Decl *decl,
234235

235236
decl = decl->getAbstractSyntaxDeclForAttributes();
236237

237-
getAvailabilityConstraintsForDecl(constraints, decl, context);
238+
getAvailabilityConstraintsForDecl(constraints, decl, context, flags);
238239

239240
if (flags.contains(AvailabilityConstraintFlag::SkipEnclosingExtension))
240241
return constraints;
@@ -251,7 +252,7 @@ swift::getAvailabilityConstraintsForDecl(const Decl *decl,
251252

252253
auto parent = AvailabilityInference::parentDeclForInferredAvailability(decl);
253254
if (auto extension = dyn_cast_or_null<ExtensionDecl>(parent))
254-
getAvailabilityConstraintsForDecl(constraints, extension, context);
255+
getAvailabilityConstraintsForDecl(constraints, extension, context, flags);
255256

256257
return constraints;
257258
}

test/SILGen/unavailable_decl_optimization_stub.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,9 @@ public func obsoletedInSwift1() {}
9090
// CHECK: } // end sil function '$s4Test17obsoletedInSwift5yyF'
9191
@available(swift, obsoleted: 5)
9292
public func obsoletedInSwift5() {}
93+
94+
// CHECK-LABEL: sil{{.*}}@$s4Test19introducedInSwift99yyF : $@convention(thin) () -> () {
95+
// CHECK-NOT: ss36_diagnoseUnavailableCodeReached
96+
// CHECK: } // end sil function '$s4Test19introducedInSwift99yyF'
97+
@available(swift, introduced: 99)
98+
public func introducedInSwift99() {}

test/SILGen/unavailable_decl_optimization_stub_macos.swift

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %target-swift-emit-silgen -module-name Test -parse-as-library %s -verify -unavailable-decl-optimization=stub | %FileCheck %s --check-prefixes=CHECK
2-
// RUN: %target-swift-emit-silgen -target %target-swift-5.8-abi-triple -module-name Test -parse-as-library %s -verify -unavailable-decl-optimization=stub -application-extension | %FileCheck %s --check-prefixes=CHECK
1+
// RUN: %target-swift-emit-silgen -module-name Test -parse-as-library %s -verify -unavailable-decl-optimization=stub | %FileCheck %s --check-prefixes=CHECK,CHECK-NO-EXTENSION
2+
// RUN: %target-swift-emit-silgen -target %target-swift-5.8-abi-triple -module-name Test -parse-as-library %s -verify -unavailable-decl-optimization=stub -application-extension | %FileCheck %s --check-prefixes=CHECK,CHECK-EXTENSION
33

44
// REQUIRES: OS=macosx
55

@@ -38,8 +38,10 @@ public func unavailableOnMacOSExtensionFunc() {}
3838
@available(macOSApplicationExtension, unavailable) // FIXME: Seems like this should be diagnosed as redundant
3939
public func unavailableOnMacOSAndMacOSExtensionFunc() {}
4040

41-
// CHECK-LABEL: sil{{.*}}@$s4Test33availableOnMacOSExtensionOnlyFuncyyF
42-
// CHECK-NOT: _diagnoseUnavailableCodeReached
41+
// CHECK-LABEL: sil{{.*}}@$s4Test33availableOnMacOSExtensionOnlyFuncyyF
42+
// CHECK-NO-EXTENSION: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyFTwb : $@convention(thin) () -> Never
43+
// CHECK-NO-EXTENSION-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
44+
// CHECK-EXTENSION-NOT: _diagnoseUnavailableCodeReached
4345
// CHECK: } // end sil function '$s4Test33availableOnMacOSExtensionOnlyFuncyyF'
4446
@available(macOS, unavailable)
4547
@available(macOSApplicationExtension, introduced: 10.9)
@@ -56,3 +58,44 @@ public func unavailableOniOSFunc() {}
5658
// CHECK: } // end sil function '$s4Test20obsoletedOnMacOS10_9yyF'
5759
@available(macOS, obsoleted: 10.9)
5860
public func obsoletedOnMacOS10_9() {}
61+
62+
// CHECK-LABEL: sil{{.*}}@$s4Test19introducedInMacOS99yyF
63+
// CHECK-NOT: _diagnoseUnavailableCodeReached
64+
// CHECK: } // end sil function '$s4Test19introducedInMacOS99yyF'
65+
@available(macOS, introduced: 99)
66+
public func introducedInMacOS99() {}
67+
68+
// CHECK-LABEL: sil{{.*}}@$s4Test28unavailableIntroducedInMacOSyyF
69+
// CHECK: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyFTwb : $@convention(thin) () -> Never
70+
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
71+
// CHECK: } // end sil function '$s4Test28unavailableIntroducedInMacOSyyF'
72+
@available(macOS, unavailable, introduced: 10.9)
73+
public func unavailableIntroducedInMacOS() {}
74+
75+
// CHECK-LABEL: sil{{.*}}@$s4Test31unavailableAndIntroducedInMacOSyyF
76+
// CHECK: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyFTwb : $@convention(thin) () -> Never
77+
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
78+
// CHECK: } // end sil function '$s4Test31unavailableAndIntroducedInMacOSyyF'
79+
@available(macOS, unavailable)
80+
@available(macOS, introduced: 10.9)
81+
public func unavailableAndIntroducedInMacOS() {}
82+
83+
// CHECK-LABEL: sil{{.*}}@$s4Test31introducedAndUnavailableInMacOSyyF
84+
// CHECK: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyFTwb : $@convention(thin) () -> Never
85+
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
86+
// CHECK: } // end sil function '$s4Test31introducedAndUnavailableInMacOSyyF'
87+
@available(macOS, introduced: 10.9)
88+
@available(macOS, unavailable)
89+
public func introducedAndUnavailableInMacOS() {}
90+
91+
// CHECK-LABEL: sil{{.*}}@$s4Test17deprecatedInMacOSyyF
92+
// CHECK-NOT: _diagnoseUnavailableCodeReached
93+
// CHECK: } // end sil function '$s4Test17deprecatedInMacOSyyF'
94+
@available(macOS, deprecated)
95+
public func deprecatedInMacOS() {}
96+
97+
// CHECK-LABEL: sil{{.*}}@$s4Test21deprecatedInMacOS10_9yyF
98+
// CHECK-NOT: _diagnoseUnavailableCodeReached
99+
// CHECK: } // end sil function '$s4Test21deprecatedInMacOS10_9yyF'
100+
@available(macOS, deprecated: 10.9)
101+
public func deprecatedInMacOS10_9() {}

0 commit comments

Comments
 (0)