Skip to content

Commit d9d4fec

Browse files
authored
Merge pull request #82773 from tshortli/refactor-availability-query-silgen
SILGen: Generalize availability query emission
2 parents 27e6573 + 6e08b89 commit d9d4fec

File tree

9 files changed

+261
-256
lines changed

9 files changed

+261
-256
lines changed

include/swift/AST/Attr.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2925,7 +2925,7 @@ class BackDeployedAttr : public DeclAttribute {
29252925
: DeclAttribute(DeclAttrKind::BackDeployed, AtLoc, Range, Implicit),
29262926
Platform(Platform), Version(Version) {}
29272927

2928-
/// The platform the symbol is available for back deployment on.
2928+
/// The platform the decl is available for back deployment in.
29292929
const PlatformKind Platform;
29302930

29312931
/// The earliest platform version that may use the back deployed implementation.
@@ -2934,6 +2934,12 @@ class BackDeployedAttr : public DeclAttribute {
29342934
/// Returns true if this attribute is active given the current platform.
29352935
bool isActivePlatform(const ASTContext &ctx, bool forTargetVariant) const;
29362936

2937+
/// Returns the `AvailabilityDomain` that the decl is available for back
2938+
/// deployment in.
2939+
AvailabilityDomain getAvailabilityDomain() const {
2940+
return AvailabilityDomain::forPlatform(Platform);
2941+
}
2942+
29372943
static bool classof(const DeclAttribute *DA) {
29382944
return DA->getKind() == DeclAttrKind::BackDeployed;
29392945
}

include/swift/AST/AvailabilityQuery.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
#include "llvm/Support/VersionTuple.h"
2424

2525
namespace swift {
26+
class ASTContext;
27+
class FuncDecl;
28+
2629
/// Represents the information needed to evaluate an `#if available` query
2730
/// (either at runtime or compile-time).
2831
class AvailabilityQuery final {
@@ -121,6 +124,14 @@ class AvailabilityQuery final {
121124
return std::nullopt;
122125
return variantRange->getRawMinimumVersion();
123126
}
127+
128+
/// Returns the `FuncDecl *` that should be invoked at runtime to evaluate
129+
/// the query, and populates `arguments` with the arguments to invoke it with
130+
/// (the integer components of the version tuples that are being tested). If
131+
/// the query does not have a dynamic result, returns `nullptr`.
132+
FuncDecl *
133+
getDynamicQueryDeclAndArguments(llvm::SmallVectorImpl<unsigned> &arguments,
134+
ASTContext &ctx) const;
124135
};
125136

126137
} // end namespace swift

include/swift/AST/Decl.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,11 +1115,11 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl>, public Swi
11151115
std::optional<llvm::VersionTuple>
11161116
getIntroducedOSVersion(PlatformKind Kind) const;
11171117

1118-
/// Returns the OS version in which the decl became ABI as specified by the
1119-
/// `@backDeployed` attribute.
1120-
std::optional<llvm::VersionTuple>
1121-
getBackDeployedBeforeOSVersion(ASTContext &Ctx,
1122-
bool forTargetVariant = false) const;
1118+
/// Returns the active `@backDeployed` attribute and the `AvailabilityRange`
1119+
/// in which the decl is available as ABI.
1120+
std::optional<std::pair<const BackDeployedAttr *, AvailabilityRange>>
1121+
getBackDeployedAttrAndRange(ASTContext &Ctx,
1122+
bool forTargetVariant = false) const;
11231123

11241124
/// Returns true if the decl has an active `@backDeployed` attribute for the
11251125
/// given context.

lib/AST/Availability.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,8 @@ bool AvailabilityInference::updateBeforeAvailabilityDomainForFallback(
365365
const BackDeployedAttr *attr, const ASTContext &ctx,
366366
AvailabilityDomain &domain, llvm::VersionTuple &platformVer) {
367367
bool hasRemap = false;
368-
auto remappedDomain = AvailabilityDomain::forPlatform(attr->Platform)
369-
.getRemappedDomain(ctx, hasRemap);
368+
auto remappedDomain =
369+
attr->getAvailabilityDomain().getRemappedDomain(ctx, hasRemap);
370370
if (!hasRemap)
371371
return false;
372372

lib/AST/AvailabilityQuery.cpp

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//===--- AvailabilityQuery.cpp - Swift Availability Queries ---------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "swift/AST/AvailabilityQuery.h"
14+
#include "swift/AST/ASTContext.h"
15+
#include "swift/AST/Decl.h"
16+
#include "swift/Basic/Platform.h"
17+
18+
using namespace swift;
19+
20+
static void unpackVersion(const llvm::VersionTuple &version,
21+
llvm::SmallVectorImpl<unsigned> &arguments) {
22+
arguments.push_back(version.getMajor());
23+
arguments.push_back(version.getMinor().value_or(0));
24+
arguments.push_back(version.getSubminor().value_or(0));
25+
}
26+
27+
static FuncDecl *
28+
getOSVersionRangeCheck(const llvm::VersionTuple &version,
29+
llvm::SmallVectorImpl<unsigned> &arguments,
30+
ASTContext &ctx, bool forTargetVariant) {
31+
unpackVersion(version, arguments);
32+
return forTargetVariant ? ctx.getIsVariantOSVersionAtLeastDecl()
33+
: ctx.getIsOSVersionAtLeastDecl();
34+
}
35+
36+
static FuncDecl *getOSVersionOrVariantVersionRangeCheck(
37+
const llvm::VersionTuple &targetVersion,
38+
const llvm::VersionTuple &variantVersion,
39+
llvm::SmallVectorImpl<unsigned> &arguments, ASTContext &ctx) {
40+
unpackVersion(targetVersion, arguments);
41+
unpackVersion(variantVersion, arguments);
42+
return ctx.getIsOSVersionAtLeastOrVariantVersionAtLeast();
43+
}
44+
45+
static FuncDecl *
46+
getZipperedOSVersionRangeCheck(const AvailabilityQuery &query,
47+
llvm::SmallVectorImpl<unsigned> &arguments,
48+
ASTContext &ctx) {
49+
50+
auto targetVersion = query.getPrimaryArgument();
51+
auto variantVersion = query.getVariantArgument();
52+
DEBUG_ASSERT(targetVersion || variantVersion);
53+
54+
// We're building zippered, so we need to pass both macOS and iOS versions to
55+
// the runtime version range check. At run time that check will determine what
56+
// kind of process this code is loaded into. In a macOS process it will use
57+
// the macOS version; in an macCatalyst process it will use the iOS version.
58+
llvm::Triple targetTriple = ctx.LangOpts.Target;
59+
llvm::Triple variantTriple = *ctx.LangOpts.TargetVariant;
60+
61+
// From perspective of the driver and most of the frontend, -target and
62+
// -target-variant are symmetric. That is, the user can pass either:
63+
// -target x86_64-apple-macosx10.15 \
64+
// -target-variant x86_64-apple-ios13.1-macabi
65+
// or:
66+
// -target x86_64-apple-ios13.1-macabi \
67+
// -target-variant x86_64-apple-macosx10.15
68+
//
69+
// However, the runtime availability-checking entry points need to compare
70+
// against an actual running OS version and so can't be symmetric. Here we
71+
// standardize on "target" means macOS version and "targetVariant" means iOS
72+
// version.
73+
if (tripleIsMacCatalystEnvironment(targetTriple)) {
74+
DEBUG_ASSERT(variantTriple.isMacOSX());
75+
// Normalize so that "variant" always means iOS version.
76+
std::swap(targetVersion, variantVersion);
77+
std::swap(targetTriple, variantTriple);
78+
}
79+
80+
// The variant-only availability-checking entrypoint is not part of the
81+
// Swift 5.0 ABI. It is only available in macOS 10.15 and above.
82+
bool isVariantEntrypointAvailable = !targetTriple.isMacOSXVersionLT(10, 15);
83+
84+
// If there is no check for the target but there is for the variant, then we
85+
// only need to emit code for the variant check.
86+
if (isVariantEntrypointAvailable && !targetVersion && variantVersion)
87+
return getOSVersionRangeCheck(*variantVersion, arguments, ctx,
88+
/*forVariant=*/true);
89+
90+
// Similarly, if there is a check for the target but not for the target
91+
// variant then we only to emit code for the target check.
92+
if (targetVersion && !variantVersion)
93+
return getOSVersionRangeCheck(*targetVersion, arguments, ctx,
94+
/*forTargetVariant=*/false);
95+
96+
if (!isVariantEntrypointAvailable || (targetVersion && variantVersion)) {
97+
98+
// If the variant-only entrypoint isn't available (as is the case
99+
// pre-macOS 10.15) we need to use the zippered entrypoint (which is part of
100+
// the Swift 5.0 ABI) even when the macOS version is '*' (all). In this
101+
// case, use the minimum macOS deployment version from the target triple.
102+
// This ensures the check always passes on macOS.
103+
if (!isVariantEntrypointAvailable && !targetVersion) {
104+
DEBUG_ASSERT(targetTriple.isMacOSX());
105+
106+
llvm::VersionTuple macosVersion;
107+
targetTriple.getMacOSXVersion(macosVersion);
108+
targetVersion = macosVersion;
109+
}
110+
111+
return getOSVersionOrVariantVersionRangeCheck(
112+
*targetVersion, *variantVersion, arguments, ctx);
113+
}
114+
115+
llvm_unreachable("Unhandled zippered configuration");
116+
}
117+
118+
static FuncDecl *
119+
getOSAvailabilityDeclAndArguments(const AvailabilityQuery &query,
120+
llvm::SmallVectorImpl<unsigned> &arguments,
121+
ASTContext &ctx) {
122+
if (ctx.LangOpts.TargetVariant)
123+
return getZipperedOSVersionRangeCheck(query, arguments, ctx);
124+
125+
bool isMacCatalyst = tripleIsMacCatalystEnvironment(ctx.LangOpts.Target);
126+
return getOSVersionRangeCheck(query.getPrimaryArgument().value(), arguments,
127+
ctx, isMacCatalyst);
128+
}
129+
130+
FuncDecl *AvailabilityQuery::getDynamicQueryDeclAndArguments(
131+
llvm::SmallVectorImpl<unsigned> &arguments, ASTContext &ctx) const {
132+
switch (getDomain().getKind()) {
133+
case AvailabilityDomain::Kind::Universal:
134+
case AvailabilityDomain::Kind::SwiftLanguage:
135+
case AvailabilityDomain::Kind::PackageDescription:
136+
case AvailabilityDomain::Kind::Embedded:
137+
return nullptr;
138+
case AvailabilityDomain::Kind::Platform:
139+
return getOSAvailabilityDeclAndArguments(*this, arguments, ctx);
140+
case AvailabilityDomain::Kind::Custom:
141+
// FIXME: [availability] Support custom domains.
142+
return nullptr;
143+
}
144+
}

lib/AST/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ add_swift_host_library(swiftAST STATIC
3030
AvailabilityConstraint.cpp
3131
AvailabilityContext.cpp
3232
AvailabilityDomain.cpp
33+
AvailabilityQuery.cpp
3334
AvailabilityScope.cpp
3435
AvailabilityScopeBuilder.cpp
3536
AvailabilitySpec.cpp

lib/AST/Decl.cpp

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -607,9 +607,9 @@ Decl::getIntroducedOSVersion(PlatformKind Kind) const {
607607
return std::nullopt;
608608
}
609609

610-
std::optional<llvm::VersionTuple>
611-
Decl::getBackDeployedBeforeOSVersion(ASTContext &Ctx,
612-
bool forTargetVariant) const {
610+
std::optional<std::pair<const BackDeployedAttr *, AvailabilityRange>>
611+
Decl::getBackDeployedAttrAndRange(ASTContext &Ctx,
612+
bool forTargetVariant) const {
613613
if (auto *attr = getAttrs().getBackDeployed(Ctx, forTargetVariant)) {
614614
auto version = attr->Version;
615615
AvailabilityDomain ignoredDomain;
@@ -618,26 +618,25 @@ Decl::getBackDeployedBeforeOSVersion(ASTContext &Ctx,
618618

619619
// If the remap for fallback resulted in 1.0, then the
620620
// backdeployment prior to that is not meaningful.
621-
if (version == clang::VersionTuple(1, 0, 0, 0))
621+
if (version == llvm::VersionTuple(1, 0, 0, 0))
622622
return std::nullopt;
623623

624-
return version;
624+
return std::make_pair(attr, AvailabilityRange(version));
625625
}
626626

627627
// Accessors may inherit `@backDeployed`.
628628
if (auto *AD = dyn_cast<AccessorDecl>(this))
629-
return AD->getStorage()->getBackDeployedBeforeOSVersion(Ctx,
630-
forTargetVariant);
629+
return AD->getStorage()->getBackDeployedAttrAndRange(Ctx, forTargetVariant);
631630

632631
return std::nullopt;
633632
}
634633

635634
bool Decl::isBackDeployed(ASTContext &Ctx) const {
636-
if (getBackDeployedBeforeOSVersion(Ctx))
635+
if (getBackDeployedAttrAndRange(Ctx))
637636
return true;
638637

639638
if (Ctx.LangOpts.TargetVariant) {
640-
if (getBackDeployedBeforeOSVersion(Ctx, /*forTargetVariant=*/true))
639+
if (getBackDeployedAttrAndRange(Ctx, /*forTargetVariant=*/true))
641640
return true;
642641
}
643642

@@ -1463,8 +1462,8 @@ AvailabilityRange Decl::getAvailabilityForLinkage() const {
14631462

14641463
// When computing availability for linkage, use the "before" version from
14651464
// the @backDeployed attribute, if present.
1466-
if (auto backDeployVersion = getBackDeployedBeforeOSVersion(ctx))
1467-
return AvailabilityRange{*backDeployVersion};
1465+
if (auto backDeployedAttrAndRange = getBackDeployedAttrAndRange(ctx))
1466+
return backDeployedAttrAndRange->second;
14681467

14691468
auto containingContext = AvailabilityInference::annotatedAvailableRange(this);
14701469
if (containingContext.has_value()) {

0 commit comments

Comments
 (0)