Skip to content

Commit 5e0eb09

Browse files
committed
AST: Skip codegen for decls that are unavailable in custom domains.
Regardless of the value specified for `-unavailable-decl-optimization`, decls that are unavailable in custom availability domains should be treated as always unreachable at runtime. Part of rdar://138441307.
1 parent 7ae3a86 commit 5e0eb09

8 files changed

+292
-43
lines changed

include/swift/AST/AvailabilityDomain.h

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,6 @@ class AvailabilityDomain final {
123123
: std::nullopt;
124124
}
125125

126-
const CustomAvailabilityDomain *getCustomDomain() const {
127-
ASSERT(isCustom());
128-
return storage.get<const CustomAvailabilityDomain *>();
129-
}
130-
131126
public:
132127
AvailabilityDomain() {}
133128

@@ -206,6 +201,14 @@ class AvailabilityDomain final {
206201
return PlatformKind::none;
207202
}
208203

204+
/// If the domain represents a user-defined domain, returns the metadata for
205+
/// the domain. Returns `nullptr` otherwise.
206+
const CustomAvailabilityDomain *getCustomDomain() const {
207+
if (isCustom())
208+
return storage.get<const CustomAvailabilityDomain *>();
209+
return nullptr;
210+
}
211+
209212
/// Returns true if availability for this domain can be specified in terms of
210213
/// version ranges.
211214
bool isVersioned() const;
@@ -228,11 +231,11 @@ class AvailabilityDomain final {
228231
/// set of active platform-specific domains.
229232
bool isActiveForTargetPlatform(const ASTContext &ctx) const;
230233

231-
/// Returns the minimum available range for the attribute's domain. For
234+
/// Returns the domain's minimum available range for type checking. For
232235
/// example, for the domain of the platform that compilation is targeting,
233-
/// this will be the deployment target. For the Swift language domain, this
234-
/// will be the language mode for compilation. For domains which have don't
235-
/// have a "deployment target", this returns `std::nullopt`.
236+
/// this version is specified with the `-target` option. For the Swift
237+
/// language domain, it is specified with the `-swift-version` option. Returns
238+
/// `std::nullopt` for domains which have don't have a "deployment target".
236239
std::optional<AvailabilityRange>
237240
getDeploymentRange(const ASTContext &ctx) const;
238241

include/swift/AST/TypeCheckRequests.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4153,6 +4153,10 @@ enum class DeclRuntimeAvailability : uint8_t {
41534153
/// with strong references. To preserve ABI stability, the decl must still be
41544154
/// emitted.
41554155
AlwaysUnavailableABICompatible,
4156+
4157+
/// The decl is always unavailable and should never be reachable at runtime
4158+
/// nor be required at load time.
4159+
AlwaysUnavailable,
41564160
};
41574161

41584162
class DeclRuntimeAvailabilityRequest

lib/AST/Availability.cpp

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -563,28 +563,43 @@ getRootTargetDomains(const ASTContext &ctx) {
563563

564564
static bool constraintIndicatesRuntimeUnavailability(
565565
const AvailabilityConstraint &constraint, const ASTContext &ctx) {
566+
std::optional<CustomAvailabilityDomain::Kind> customDomainKind;
567+
if (auto customDomain = constraint.getDomain().getCustomDomain())
568+
customDomainKind = customDomain->getKind();
569+
566570
switch (constraint.getReason()) {
567-
case AvailabilityConstraint::Reason::UnconditionallyUnavailable: {
571+
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
572+
if (customDomainKind)
573+
return customDomainKind == CustomAvailabilityDomain::Kind::Enabled;
568574
return true;
569-
}
570-
case AvailabilityConstraint::Reason::UnavailableForDeployment:
571575
case AvailabilityConstraint::Reason::Obsoleted:
576+
case AvailabilityConstraint::Reason::UnavailableForDeployment:
577+
return false;
572578
case AvailabilityConstraint::Reason::PotentiallyUnavailable:
579+
if (customDomainKind)
580+
return customDomainKind == CustomAvailabilityDomain::Kind::Disabled;
573581
return false;
574582
}
575583
}
576584

585+
/// Returns true if a decl that is unavailable in the given domain must still be
586+
/// emitted to preserve load time ABI compatibility.
587+
static bool
588+
domainRequiresABICompatibleUnavailableDecls(AvailabilityDomain domain,
589+
const ASTContext &ctx) {
590+
// FIXME: [availability] Restrict ABI compatible unavailable decls to modules
591+
// compiled with macOS, iOS, watchOS, tvOS, or visionOS target triples. For
592+
// other targets, unavailable code should always be stripped from binaries.
593+
return domain.isUniversal() || domain.isPlatform();
594+
}
595+
577596
/// Computes the `DeclRuntimeAvailability` value for `decl` in isolation.
578597
static DeclRuntimeAvailability
579598
computeDeclRuntimeAvailability(const Decl *decl) {
580599
// Don't trust unavailability on declarations from Clang modules.
581600
if (isa<ClangModuleUnit>(decl->getDeclContext()->getModuleScopeContext()))
582601
return DeclRuntimeAvailability::PotentiallyAvailable;
583602

584-
// Check whether the decl is unavailable at all.
585-
if (!decl->isUnavailable())
586-
return DeclRuntimeAvailability::PotentiallyAvailable;
587-
588603
auto &ctx = decl->getASTContext();
589604
auto rootTargetDomains = getRootTargetDomains(ctx);
590605
auto remainingTargetDomains = rootTargetDomains;
@@ -595,41 +610,67 @@ computeDeclRuntimeAvailability(const Decl *decl) {
595610
// extension.
596611
flags |= AvailabilityConstraintFlag::SkipEnclosingExtension;
597612

598-
// FIXME: [availability] Inactive domains have to be included because iOS
599-
// availability is considered inactive when compiling a zippered module.
613+
// FIXME: [availability] Replace IncludeAllDomains with a RuntimeAvailability
614+
// flag that includes the target variant constraints and keeps all constraints
615+
// from active platforms.
600616
flags |= AvailabilityConstraintFlag::IncludeAllDomains;
601617

602618
auto constraints = getAvailabilityConstraintsForDecl(
603619
decl, AvailabilityContext::forInliningTarget(ctx), flags);
604620

621+
// First, collect the unavailable domains from the constraints.
622+
llvm::SmallVector<AvailabilityDomain, 8> unavailableDomains;
605623
for (auto constraint : constraints) {
606-
if (!constraintIndicatesRuntimeUnavailability(constraint, ctx))
624+
if (constraintIndicatesRuntimeUnavailability(constraint, ctx))
625+
unavailableDomains.push_back(constraint.getDomain());
626+
}
627+
628+
// Check whether there are any available attributes that would make the
629+
// decl available in descendants of the unavailable domains.
630+
for (auto attr :
631+
decl->getSemanticAvailableAttrs(/*includingInactive=*/false)) {
632+
auto domain = attr.getDomain();
633+
if (llvm::is_contained(unavailableDomains, domain))
607634
continue;
608635

636+
llvm::erase_if(unavailableDomains, [domain](auto unavailableDomain) {
637+
return unavailableDomain.contains(domain);
638+
});
639+
}
640+
641+
// Check the remaining unavailable domains to see if the requirements for
642+
// runtime unreachability are met.
643+
auto result = DeclRuntimeAvailability::PotentiallyAvailable;
644+
for (auto domain : unavailableDomains) {
609645
// Check whether the constraint is from a relevant domain.
610-
auto domain = constraint.getDomain();
611646
bool isTargetDomain = rootTargetDomains.contains(domain);
612-
613647
if (!domain.isActive(ctx) && !isTargetDomain)
614648
continue;
615649

616650
if (!domain.isRoot())
617651
continue;
618652

653+
// We've found an unavailable target domain. If all the target domains are
654+
// unavailable then the decl is unreachable at runtime.
619655
if (isTargetDomain) {
620-
// If the decl is still potentially available in some compatibility
621-
// domain, keep looking at the remaining constraints.
622656
remainingTargetDomains.remove(domain);
623-
if (!remainingTargetDomains.empty())
624-
continue;
657+
if (remainingTargetDomains.empty())
658+
result = DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
659+
660+
continue;
625661
}
626662

627-
// Either every compatibility domain has been proven unavailable or this
628-
// constraint proves runtime unavailability on its own.
629-
return DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
663+
// We've found a single unavailable domain that alone proves the decl is
664+
// unreachable at runtime. It may still be required at load time, though.
665+
if (domainRequiresABICompatibleUnavailableDecls(domain, ctx)) {
666+
result = DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
667+
continue;
668+
}
669+
670+
return DeclRuntimeAvailability::AlwaysUnavailable;
630671
}
631672

632-
return DeclRuntimeAvailability::PotentiallyAvailable;
673+
return result;
633674
}
634675

635676
/// Determines the `DeclRuntimeAvailability` value for `decl` via
@@ -651,8 +692,8 @@ DeclRuntimeAvailabilityRequest::evaluate(Evaluator &evaluator,
651692

652693
// If the inherited runtime availability is already maximally unavailable
653694
// then skip computing unavailability for this declaration.
654-
if (inherited == DeclRuntimeAvailability::AlwaysUnavailableABICompatible)
655-
return DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
695+
if (inherited == DeclRuntimeAvailability::AlwaysUnavailable)
696+
return DeclRuntimeAvailability::AlwaysUnavailable;
656697

657698
auto availability = computeDeclRuntimeAvailability(decl);
658699
return std::max(inherited, availability);
@@ -676,7 +717,7 @@ bool Decl::isAvailableDuringLowering() const {
676717

677718
if (getEffectiveUnavailableDeclOptimization(getASTContext()) !=
678719
UnavailableDeclOptimization::Complete)
679-
return true;
720+
return availability < DeclRuntimeAvailability::AlwaysUnavailable;
680721

681722
// All unreachable declarations should be skipped during lowering
682723
// when -unavailable-decl-optimization=complete is specified.

lib/Frontend/Frontend.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1398,8 +1398,7 @@ static void configureAvailabilityDomains(const ASTContext &ctx,
13981398
llvm::SmallDenseMap<Identifier, const CustomAvailabilityDomain *> domainMap;
13991399
auto createAndInsertDomain = [&](const std::string &name,
14001400
CustomAvailabilityDomain::Kind kind) {
1401-
auto *domain = CustomAvailabilityDomain::get(
1402-
name, mainModule, CustomAvailabilityDomain::Kind::Enabled, ctx);
1401+
auto *domain = CustomAvailabilityDomain::get(name, mainModule, kind, ctx);
14031402
bool inserted = domainMap.insert({domain->getName(), domain}).second;
14041403
ASSERT(inserted); // Domains must be unique.
14051404
};
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// RUN: %target-swift-emit-silgen -module-name Test %s -verify \
2+
// RUN: -enable-experimental-feature CustomAvailability \
3+
// RUN: -define-enabled-availability-domain EnabledDomain \
4+
// RUN: -define-disabled-availability-domain DisabledDomain \
5+
// RUN: -define-dynamic-availability-domain DynamicDomain \
6+
// RUN: | %FileCheck %s --check-prefixes=CHECK
7+
8+
// RUN: %target-swift-emit-silgen -module-name Test %s -verify \
9+
// RUN: -enable-experimental-feature CustomAvailability \
10+
// RUN: -define-enabled-availability-domain EnabledDomain \
11+
// RUN: -define-disabled-availability-domain DisabledDomain \
12+
// RUN: -define-dynamic-availability-domain DynamicDomain \
13+
// RUN: -unavailable-decl-optimization=stub \
14+
// RUN: | %FileCheck %s --check-prefixes=CHECK
15+
16+
// RUN: %target-swift-emit-silgen -module-name Test %s -verify \
17+
// RUN: -enable-experimental-feature CustomAvailability \
18+
// RUN: -define-enabled-availability-domain EnabledDomain \
19+
// RUN: -define-disabled-availability-domain DisabledDomain \
20+
// RUN: -define-dynamic-availability-domain DynamicDomain \
21+
// RUN: -unavailable-decl-optimization=complete \
22+
// RUN: | %FileCheck %s --check-prefixes=CHECK
23+
24+
// REQUIRES: swift_feature_CustomAvailability
25+
26+
// CHECK: s4Test24availableInEnabledDomainyyF
27+
@available(EnabledDomain)
28+
public func availableInEnabledDomain() { }
29+
30+
// CHECK-NOT: s4Test26unavailableInEnabledDomainyyF
31+
@available(EnabledDomain, unavailable)
32+
public func unavailableInEnabledDomain() { }
33+
34+
// CHECK-NOT: s4Test25availableInDisabledDomainyyF
35+
@available(DisabledDomain)
36+
public func availableInDisabledDomain() { }
37+
38+
// CHECK: s4Test27unavailableInDisabledDomainyyF
39+
@available(DisabledDomain, unavailable)
40+
public func unavailableInDisabledDomain() { }
41+
42+
// CHECK: s4Test24availableInDynamicDomainyyF
43+
@available(DynamicDomain)
44+
public func availableInDynamicDomain() { }
45+
46+
// CHECK: s4Test26unavailableInDynamicDomainyyF
47+
@available(DynamicDomain, unavailable)
48+
public func unavailableInDynamicDomain() { }
49+
50+
// CHECK: s4Test25deprecatedInEnabledDomainyyF
51+
@available(EnabledDomain, deprecated)
52+
public func deprecatedInEnabledDomain() { }
53+
54+
// FIXME: [availability] This decl should be skipped.
55+
// CHECK: s4Test26deprecatedInDisabledDomainyyF
56+
@available(DisabledDomain, deprecated)
57+
public func deprecatedInDisabledDomain() { }
58+
59+
// CHECK: s4Test25deprecatedInDynamicDomainyyF
60+
@available(DynamicDomain, deprecated)
61+
public func deprecatedInDynamicDomain() { }
62+
63+
// CHECK: s4Test22renamedInEnabledDomainyyF
64+
@available(EnabledDomain, renamed: "availableInEnabledDomain")
65+
public func renamedInEnabledDomain() { }
66+
67+
// CHECK-NOT: s4Test23renamedInDisabledDomainyyF
68+
@available(DisabledDomain, renamed: "availableInDisabledDomain")
69+
public func renamedInDisabledDomain() { }
70+
71+
// CHECK: s4Test22renamedInDynamicDomainyyF
72+
@available(DynamicDomain, renamed: "availableInDynamicDomain")
73+
public func renamedInDynamicDomain() { }
74+
75+
// CHECK-NOT: s4Test35availableInEnabledAndDisabledDomainyyF
76+
@available(EnabledDomain)
77+
@available(DisabledDomain)
78+
public func availableInEnabledAndDisabledDomain() { }
79+
80+
// CHECK-NOT: s4Test35availableInDisabledAndEnabledDomainyyF
81+
@available(DisabledDomain)
82+
@available(EnabledDomain)
83+
public func availableInDisabledAndEnabledDomain() { }
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// RUN: %target-swift-emit-silgen -module-name Test -parse-as-library %s -verify -unavailable-decl-optimization=stub -target %target-cpu-apple-ios13.1-macabi | %FileCheck %s --check-prefixes=CHECK,CHECK-NO-EXTENSION
2+
// RUN: %target-swift-emit-silgen -module-name Test -parse-as-library %s -verify -unavailable-decl-optimization=stub -target %target-cpu-apple-ios13.1-macabi -application-extension | %FileCheck %s --check-prefixes=CHECK,CHECK-EXTENSION
3+
4+
// REQUIRES: OS=macosx || OS=maccatalyst
5+
6+
public struct S {}
7+
8+
// CHECK-LABEL: sil{{.*}}@$s4Test22macCatalystUnavailableAA1SVyF
9+
// CHECK-NOT: _diagnoseUnavailableCodeReached
10+
// CHECK: } // end sil function '$s4Test22macCatalystUnavailableAA1SVyF'
11+
@available(macCatalyst, unavailable)
12+
public func macCatalystUnavailable() -> S {
13+
return S()
14+
}
15+
16+
// CHECK-LABEL: sil{{.*}}@$s4Test14iOSUnavailableAA1SVyF
17+
// CHECK: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyFTwb : $@convention(thin) () -> Never
18+
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
19+
// CHECK: } // end sil function '$s4Test14iOSUnavailableAA1SVyF'
20+
@available(iOS, unavailable)
21+
public func iOSUnavailable() -> S {
22+
return S()
23+
}
24+
25+
// CHECK-LABEL: sil{{.*}}@$s4Test16macOSUnavailableAA1SVyF
26+
// CHECK-NOT: _diagnoseUnavailableCodeReached
27+
// CHECK: } // end sil function '$s4Test16macOSUnavailableAA1SVyF'
28+
@available(macOS, unavailable)
29+
public func macOSUnavailable() -> S {
30+
return S()
31+
}
32+
33+
// CHECK-LABEL: sil{{.*}}@$s4Test34iOSUnavailableMacCatalystAvailableAA1SVyF
34+
// CHECK-NOT: _diagnoseUnavailableCodeReached
35+
// CHECK: } // end sil function '$s4Test34iOSUnavailableMacCatalystAvailableAA1SVyF'
36+
@available(iOS, unavailable)
37+
@available(macCatalyst, introduced: 1.0)
38+
public func iOSUnavailableMacCatalystAvailable() -> S {
39+
return S()
40+
}
41+
42+
// CHECK-LABEL: sil{{.*}}@$s4Test28iOSAndMacCatalystUnavailableAA1SVyF
43+
// CHECK: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyFTwb : $@convention(thin) () -> Never
44+
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
45+
// CHECK: } // end sil function '$s4Test28iOSAndMacCatalystUnavailableAA1SVyF'
46+
@available(iOS, unavailable)
47+
@available(macCatalyst, unavailable)
48+
public func iOSAndMacCatalystUnavailable() -> S {
49+
return S()
50+
}
51+
52+
// CHECK-LABEL: sil{{.*}}@$s4Test20iOSAppExtensionsOnlyAA1SVyF
53+
// CHECK-NO-EXTENSION: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyFTwb : $@convention(thin) () -> Never
54+
// CHECK-NO-EXTENSION-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
55+
// CHECK-EXTENSION-NOT: _diagnoseUnavailableCodeReached
56+
// CHECK: } // end sil function '$s4Test20iOSAppExtensionsOnlyAA1SVyF'
57+
@available(iOS, unavailable)
58+
@available(macCatalyst, unavailable)
59+
@available(iOSApplicationExtension, introduced: 1.0)
60+
public func iOSAppExtensionsOnly() -> S {
61+
return S()
62+
}
63+
64+
// CHECK-LABEL: sil{{.*}}@$s4Test28macCatalystAppExtensionsOnlyAA1SVyF
65+
// CHECK-NO-EXTENSION: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyFTwb : $@convention(thin) () -> Never
66+
// CHECK-NO-EXTENSION-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
67+
// CHECK-EXTENSION-NOT: _diagnoseUnavailableCodeReached
68+
// CHECK: } // end sil function '$s4Test28macCatalystAppExtensionsOnlyAA1SVyF'
69+
@available(iOS, unavailable)
70+
@available(macCatalyst, unavailable)
71+
@available(macCatalystApplicationExtension, introduced: 1.0)
72+
public func macCatalystAppExtensionsOnly() -> S {
73+
return S()
74+
}
75+
76+
@available(macCatalyst, unavailable)
77+
public struct UnavailableOnMacCatalyst {
78+
// CHECK-LABEL: sil{{.*}}@$s4Test24UnavailableOnMacCatalystV14noAvailabilityAA1SVyF
79+
// CHECK-NOT: _diagnoseUnavailableCodeReached
80+
// CHECK: } // end sil function '$s4Test24UnavailableOnMacCatalystV14noAvailabilityAA1SVyF'
81+
public func noAvailability() -> S {
82+
return S()
83+
}
84+
85+
// CHECK-LABEL: sil{{.*}}@$s4Test24UnavailableOnMacCatalystV022iOSUnavailableInheritsdeB0AA1SVyF
86+
// CHECK: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyFTwb : $@convention(thin) () -> Never
87+
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
88+
// CHECK: } // end sil function '$s4Test24UnavailableOnMacCatalystV022iOSUnavailableInheritsdeB0AA1SVyF'
89+
@available(iOS, unavailable)
90+
public func iOSUnavailableInheritsMacCatalystUnavailable() -> S {
91+
return S()
92+
}
93+
}

0 commit comments

Comments
 (0)