Skip to content

Commit 7e7b234

Browse files
authored
Merge pull request #80084 from tshortli/custom-domain-runtime-availability
AST: Skip SILGen for decls that are unavailable in custom domains
2 parents ad3a5ad + 5e0eb09 commit 7e7b234

11 files changed

+380
-91
lines changed

include/swift/AST/AvailabilityDomain.h

Lines changed: 20 additions & 13 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

@@ -251,10 +254,14 @@ class AvailabilityDomain final {
251254
/// universal domain (`*`) is the bottom element.
252255
bool contains(const AvailabilityDomain &other) const;
253256

254-
/// Returns the root availability domain that this domain must be compatible
255-
/// with. For example, macCatalyst and visionOS must both be ABI compatible
256-
/// with iOS. The compatible domain must contain this domain.
257-
AvailabilityDomain getABICompatibilityDomain() const;
257+
/// Returns true for domains that are not contained by any domain other than
258+
/// the universal domain.
259+
bool isRoot() const;
260+
261+
/// Returns the root availability domain that contains this domain (see
262+
/// `isRoot()`). For example, macCatalyst and visionOS are both ultimately
263+
/// descendants of the iOS domain.
264+
AvailabilityDomain getRootDomain() const;
258265

259266
bool operator==(const AvailabilityDomain &other) const {
260267
return storage.getOpaqueValue() == other.storage.getOpaqueValue();

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: 98 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -541,8 +541,10 @@ std::optional<SemanticAvailableAttr> Decl::getUnavailableAttr() const {
541541
return std::nullopt;
542542
}
543543

544+
/// Returns the mutually exclusive root platform domains that must all be
545+
/// unavailable in order for a declaration to be unavailable at runtime.
544546
static llvm::SmallSetVector<AvailabilityDomain, 2>
545-
availabilityDomainsForABICompatibility(const ASTContext &ctx) {
547+
getRootTargetDomains(const ASTContext &ctx) {
546548
llvm::SmallSetVector<AvailabilityDomain, 2> domains;
547549

548550
// Regardless of target platform, binaries built for Embedded do not require
@@ -551,83 +553,132 @@ availabilityDomainsForABICompatibility(const ASTContext &ctx) {
551553
return domains;
552554

553555
if (auto targetDomain = AvailabilityDomain::forTargetPlatform(ctx))
554-
domains.insert(targetDomain->getABICompatibilityDomain());
556+
domains.insert(targetDomain->getRootDomain());
555557

556558
if (auto variantDomain = AvailabilityDomain::forTargetVariantPlatform(ctx))
557-
domains.insert(variantDomain->getABICompatibilityDomain());
559+
domains.insert(variantDomain->getRootDomain());
558560

559561
return domains;
560562
}
561563

562564
static bool constraintIndicatesRuntimeUnavailability(
563565
const AvailabilityConstraint &constraint, const ASTContext &ctx) {
566+
std::optional<CustomAvailabilityDomain::Kind> customDomainKind;
567+
if (auto customDomain = constraint.getDomain().getCustomDomain())
568+
customDomainKind = customDomain->getKind();
569+
564570
switch (constraint.getReason()) {
565-
case AvailabilityConstraint::Reason::UnconditionallyUnavailable: {
571+
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
572+
if (customDomainKind)
573+
return customDomainKind == CustomAvailabilityDomain::Kind::Enabled;
566574
return true;
567-
}
568-
case AvailabilityConstraint::Reason::UnavailableForDeployment:
569575
case AvailabilityConstraint::Reason::Obsoleted:
576+
case AvailabilityConstraint::Reason::UnavailableForDeployment:
577+
return false;
570578
case AvailabilityConstraint::Reason::PotentiallyUnavailable:
579+
if (customDomainKind)
580+
return customDomainKind == CustomAvailabilityDomain::Kind::Disabled;
571581
return false;
572582
}
573583
}
574584

575-
/// Computes the `DeclRuntimeAvailability` value for `decl`.
576-
static DeclRuntimeAvailability getDeclRuntimeAvailability(const Decl *decl) {
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+
596+
/// Computes the `DeclRuntimeAvailability` value for `decl` in isolation.
597+
static DeclRuntimeAvailability
598+
computeDeclRuntimeAvailability(const Decl *decl) {
577599
// Don't trust unavailability on declarations from Clang modules.
578600
if (isa<ClangModuleUnit>(decl->getDeclContext()->getModuleScopeContext()))
579601
return DeclRuntimeAvailability::PotentiallyAvailable;
580602

581-
// Check whether the decl is unavailable at all.
582-
if (!decl->isUnavailable())
583-
return DeclRuntimeAvailability::PotentiallyAvailable;
584-
585603
auto &ctx = decl->getASTContext();
586-
auto compatibilityDomains = availabilityDomainsForABICompatibility(ctx);
587-
auto potentiallyAvailableDomains = compatibilityDomains;
604+
auto rootTargetDomains = getRootTargetDomains(ctx);
605+
auto remainingTargetDomains = rootTargetDomains;
588606

589607
AvailabilityConstraintFlags flags;
590608

591609
// Semantic availability was already computed separately for any enclosing
592610
// extension.
593611
flags |= AvailabilityConstraintFlag::SkipEnclosingExtension;
594612

595-
// FIXME: [availability] Inactive domains have to be included because iOS
596-
// 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.
597616
flags |= AvailabilityConstraintFlag::IncludeAllDomains;
598617

599618
auto constraints = getAvailabilityConstraintsForDecl(
600619
decl, AvailabilityContext::forInliningTarget(ctx), flags);
601620

621+
// First, collect the unavailable domains from the constraints.
622+
llvm::SmallVector<AvailabilityDomain, 8> unavailableDomains;
602623
for (auto constraint : constraints) {
603-
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))
604634
continue;
605635

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) {
606645
// Check whether the constraint is from a relevant domain.
607-
auto domain = constraint.getDomain();
608-
bool isCompabilityDomainDescendant =
609-
llvm::find_if(compatibilityDomains,
610-
[&domain](AvailabilityDomain compatibilityDomain) {
611-
return compatibilityDomain.contains(domain);
612-
}) != compatibilityDomains.end();
613-
614-
if (!domain.isActive(ctx) && !isCompabilityDomainDescendant)
646+
bool isTargetDomain = rootTargetDomains.contains(domain);
647+
if (!domain.isActive(ctx) && !isTargetDomain)
615648
continue;
616649

617-
if (isCompabilityDomainDescendant) {
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;
650+
if (!domain.isRoot())
651+
continue;
652+
653+
// We've found an unavailable target domain. If all the target domains are
654+
// unavailable then the decl is unreachable at runtime.
655+
if (isTargetDomain) {
656+
remainingTargetDomains.remove(domain);
657+
if (remainingTargetDomains.empty())
658+
result = DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
659+
660+
continue;
623661
}
624662

625-
// Either every compatibility domain has been proven unavailable or this
626-
// constraint proves runtime unavailability on its own.
627-
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;
628671
}
629672

630-
return DeclRuntimeAvailability::PotentiallyAvailable;
673+
return result;
674+
}
675+
676+
/// Determines the `DeclRuntimeAvailability` value for `decl` via
677+
/// `DeclRuntimeAvailabilityRequest`.
678+
static DeclRuntimeAvailability getDeclRuntimeAvailability(const Decl *decl) {
679+
return evaluateOrDefault(decl->getASTContext().evaluator,
680+
DeclRuntimeAvailabilityRequest{decl},
681+
DeclRuntimeAvailability::PotentiallyAvailable);
631682
}
632683

633684
DeclRuntimeAvailability
@@ -636,24 +687,20 @@ DeclRuntimeAvailabilityRequest::evaluate(Evaluator &evaluator,
636687
auto inherited = DeclRuntimeAvailability::PotentiallyAvailable;
637688
if (auto *parent =
638689
AvailabilityInference::parentDeclForInferredAvailability(decl)) {
639-
inherited = evaluateOrDefault(
640-
evaluator, DeclRuntimeAvailabilityRequest{parent}, inherited);
690+
inherited = getDeclRuntimeAvailability(parent);
641691
}
642692

643-
// If the inherited semantic availability is already maximally unavailable
693+
// If the inherited runtime availability is already maximally unavailable
644694
// then skip computing unavailability for this declaration.
645-
if (inherited == DeclRuntimeAvailability::AlwaysUnavailableABICompatible)
646-
return DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
695+
if (inherited == DeclRuntimeAvailability::AlwaysUnavailable)
696+
return DeclRuntimeAvailability::AlwaysUnavailable;
647697

648-
auto availability = getDeclRuntimeAvailability(decl);
698+
auto availability = computeDeclRuntimeAvailability(decl);
649699
return std::max(inherited, availability);
650700
}
651701

652702
bool Decl::isUnreachableAtRuntime() const {
653-
auto availability = evaluateOrDefault(
654-
getASTContext().evaluator, DeclRuntimeAvailabilityRequest{this},
655-
DeclRuntimeAvailability::PotentiallyAvailable);
656-
return availability ==
703+
return getDeclRuntimeAvailability(this) >=
657704
DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
658705
}
659706

@@ -666,13 +713,15 @@ getEffectiveUnavailableDeclOptimization(ASTContext &ctx) {
666713
}
667714

668715
bool Decl::isAvailableDuringLowering() const {
669-
// Unconditionally unavailable declarations should be skipped during lowering
670-
// when -unavailable-decl-optimization=complete is specified.
716+
auto availability = getDeclRuntimeAvailability(this);
717+
671718
if (getEffectiveUnavailableDeclOptimization(getASTContext()) !=
672719
UnavailableDeclOptimization::Complete)
673-
return true;
720+
return availability < DeclRuntimeAvailability::AlwaysUnavailable;
674721

675-
return !isUnreachableAtRuntime();
722+
// All unreachable declarations should be skipped during lowering
723+
// when -unavailable-decl-optimization=complete is specified.
724+
return availability < DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
676725
}
677726

678727
bool Decl::requiresUnavailableDeclABICompatibilityStubs() const {

lib/AST/AvailabilityDomain.cpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,8 @@ bool AvailabilityDomain::isActive(const ASTContext &ctx) const {
121121
bool AvailabilityDomain::isActiveForTargetPlatform(
122122
const ASTContext &ctx) const {
123123
if (isPlatform()) {
124-
if (auto targetDomain = AvailabilityDomain::forTargetPlatform(ctx)) {
125-
auto compatibleDomain = targetDomain->getABICompatibilityDomain();
126-
return compatibleDomain.contains(*this);
127-
}
124+
if (auto targetDomain = AvailabilityDomain::forTargetPlatform(ctx))
125+
return targetDomain->getRootDomain().contains(*this);
128126
}
129127
return false;
130128
}
@@ -212,14 +210,31 @@ bool AvailabilityDomain::contains(const AvailabilityDomain &other) const {
212210
}
213211
}
214212

215-
AvailabilityDomain AvailabilityDomain::getABICompatibilityDomain() const {
213+
bool AvailabilityDomain::isRoot() const {
214+
switch (getKind()) {
215+
case AvailabilityDomain::Kind::Universal:
216+
case AvailabilityDomain::Kind::Embedded:
217+
case AvailabilityDomain::Kind::SwiftLanguage:
218+
case AvailabilityDomain::Kind::PackageDescription:
219+
return true;
220+
case AvailabilityDomain::Kind::Platform:
221+
return getRootDomain() == *this;
222+
case AvailabilityDomain::Kind::Custom:
223+
// For now, all custom domains are their own root.
224+
return true;
225+
}
226+
}
227+
228+
AvailabilityDomain AvailabilityDomain::getRootDomain() const {
216229
if (!isPlatform())
217230
return *this;
218231

232+
// iOS specifically contains a few other platforms.
219233
auto iOSDomain = AvailabilityDomain::forPlatform(PlatformKind::iOS);
220234
if (iOSDomain.contains(*this))
221235
return iOSDomain;
222236

237+
// App Extension domains are contained by their base platform domain.
223238
if (auto basePlatform = basePlatformForExtensionPlatform(getPlatformKind()))
224239
return AvailabilityDomain::forPlatform(*basePlatform);
225240

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
};

0 commit comments

Comments
 (0)