Skip to content

Commit ede7bdb

Browse files
authored
Merge pull request #82671 from tshortli/zippered-miscompile-if-unavailable
SILGen: Fix `if #unavailable` mis-compile for zippered libraries
2 parents d2f4e84 + 59d74fa commit ede7bdb

18 files changed

+540
-255
lines changed

include/swift/AST/AvailabilityQuery.h

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//===--- AvailabilityQuery.h - Swift Availability Query ASTs ----*- C++ -*-===//
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+
// This file defines the availability query AST classes.
14+
//
15+
//===----------------------------------------------------------------------===//
16+
17+
#ifndef SWIFT_AST_AVAILABILITY_QUERY_H
18+
#define SWIFT_AST_AVAILABILITY_QUERY_H
19+
20+
#include "swift/AST/AvailabilityDomain.h"
21+
#include "swift/AST/AvailabilityRange.h"
22+
#include "llvm/ADT/SmallVector.h"
23+
#include "llvm/Support/VersionTuple.h"
24+
25+
namespace swift {
26+
/// Represents the information needed to evaluate an `#if available` query
27+
/// (either at runtime or compile-time).
28+
class AvailabilityQuery final {
29+
AvailabilityDomain domain;
30+
std::optional<AvailabilityRange> primaryRange;
31+
std::optional<AvailabilityRange> variantRange;
32+
33+
enum class ResultKind : uint8_t {
34+
/// The result of the query is true at compile-time.
35+
ConstTrue = 0,
36+
/// The result of the query is false at compile-time.
37+
ConstFalse = 1,
38+
/// The result of the query must be determined at runtime.
39+
Dynamic = 2,
40+
};
41+
ResultKind kind;
42+
43+
bool unavailable;
44+
45+
AvailabilityQuery(AvailabilityDomain domain, ResultKind kind,
46+
bool isUnavailable,
47+
const std::optional<AvailabilityRange> &primaryRange,
48+
const std::optional<AvailabilityRange> &variantRange)
49+
: domain(domain), primaryRange(primaryRange), variantRange(variantRange),
50+
kind(kind), unavailable(isUnavailable) {};
51+
52+
public:
53+
/// Returns an `AvailabilityQuery` for a query that evaluates to true or
54+
/// false at compile-time.
55+
static AvailabilityQuery constant(AvailabilityDomain domain,
56+
bool isUnavailable, bool value) {
57+
return AvailabilityQuery(
58+
domain, value ? ResultKind::ConstTrue : ResultKind::ConstFalse,
59+
isUnavailable, std::nullopt, std::nullopt);
60+
}
61+
62+
/// Returns an `AvailabilityQuery` for a query that must be evaluated at
63+
/// runtime with the given arguments, which may be zero, one, or two version
64+
/// tuples that should be passed to the query function.
65+
static AvailabilityQuery
66+
dynamic(AvailabilityDomain domain, bool isUnavailable,
67+
const std::optional<AvailabilityRange> &primaryRange,
68+
const std::optional<AvailabilityRange> &variantRange) {
69+
return AvailabilityQuery(domain, ResultKind::Dynamic, isUnavailable,
70+
primaryRange, variantRange);
71+
}
72+
73+
/// Returns the domain that the query applies to.
74+
AvailabilityDomain getDomain() const { return domain; }
75+
76+
/// Returns true if the query's result is determined at compile-time.
77+
bool isConstant() const { return kind != ResultKind::Dynamic; }
78+
79+
/// Returns true if the query was spelled `#unavailable`.
80+
bool isUnavailability() const { return unavailable; }
81+
82+
/// Returns the boolean result of the query if it is known at compile-time, or
83+
/// `std::nullopt` otherwise. The returned value accounts for whether the
84+
/// query was spelled `#unavailable`.
85+
std::optional<bool> getConstantResult() const {
86+
switch (kind) {
87+
case ResultKind::ConstTrue:
88+
return !unavailable;
89+
case ResultKind::ConstFalse:
90+
return unavailable;
91+
case ResultKind::Dynamic:
92+
return std::nullopt;
93+
}
94+
}
95+
96+
/// Returns the availability range that is the first argument to query
97+
/// function.
98+
std::optional<AvailabilityRange> getPrimaryRange() const {
99+
return primaryRange;
100+
}
101+
102+
/// Returns the version tuple that is the first argument to query function.
103+
std::optional<llvm::VersionTuple> getPrimaryArgument() const {
104+
if (!primaryRange)
105+
return std::nullopt;
106+
return primaryRange->getRawMinimumVersion();
107+
}
108+
109+
/// Returns the availability range that is the second argument to query
110+
/// function. This represents the `-target-variant` version when compiling a
111+
/// zippered library.
112+
std::optional<AvailabilityRange> getVariantRange() const {
113+
return variantRange;
114+
}
115+
116+
/// Returns the version tuple that is the second argument to query function.
117+
/// This represents the `-target-variant` version when compiling a zippered
118+
/// library.
119+
std::optional<llvm::VersionTuple> getVariantArgument() const {
120+
if (!variantRange)
121+
return std::nullopt;
122+
return variantRange->getRawMinimumVersion();
123+
}
124+
};
125+
126+
} // end namespace swift
127+
128+
#endif

include/swift/AST/AvailabilitySpec.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//===--- AvailabilitySpec.h - Swift Availability Query ASTs -----*- C++ -*-===//
1+
//===--- AvailabilitySpec.h - Swift Availability Spec ASTs ------*- C++ -*-===//
22
//
33
// This source file is part of the Swift.org open source project
44
//

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5300,7 +5300,9 @@ GROUPED_ERROR(opaque_type_var_no_init,OpaqueTypeInference,none,
53005300
GROUPED_ERROR(opaque_type_var_no_underlying_type,OpaqueTypeInference,none,
53015301
"property declares an opaque return type, but cannot infer the "
53025302
"underlying type from its initializer expression", ())
5303-
5303+
GROUPED_ERROR(opaque_type_unsupported_availability,OpaqueTypeInference,none,
5304+
"opaque return type cannot depend on %0 availability",
5305+
(AvailabilityDomain))
53045306

53055307
//------------------------------------------------------------------------------
53065308
// MARK: Discard Statement

include/swift/AST/Stmt.h

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "swift/AST/ASTAllocated.h"
2121
#include "swift/AST/ASTNode.h"
22+
#include "swift/AST/AvailabilityQuery.h"
2223
#include "swift/AST/AvailabilityRange.h"
2324
#include "swift/AST/ConcreteDeclRef.h"
2425
#include "swift/AST/IfConfigClause.h"
@@ -477,20 +478,11 @@ class alignas(8) PoundAvailableInfo final :
477478
SourceLoc LParenLoc;
478479
SourceLoc RParenLoc;
479480

480-
// The number of queries tail allocated after this object.
481+
/// The number of queries tail allocated after this object.
481482
unsigned NumQueries;
482-
483-
/// The version range when this query will return true. This value is
484-
/// filled in by Sema.
485-
std::optional<VersionRange> AvailableRange;
486-
487-
/// For zippered builds, this is the version range for the target variant
488-
/// that must hold for the query to return true. For example, when
489-
/// compiling with target x86_64-macosx10.15 and target-variant
490-
/// x86_64-ios13.0 a query of #available(macOS 10.22, iOS 20.0, *) will
491-
/// have a variant range of [20.0, +inf).
492-
/// This is filled in by Sema.
493-
std::optional<VersionRange> VariantAvailableRange;
483+
484+
/// The type-checked availability query information.
485+
std::optional<const AvailabilityQuery> Query;
494486

495487
struct {
496488
unsigned isInvalid : 1;
@@ -504,8 +496,7 @@ class alignas(8) PoundAvailableInfo final :
504496
ArrayRef<AvailabilitySpec *> queries, SourceLoc RParenLoc,
505497
bool isUnavailability)
506498
: PoundLoc(PoundLoc), LParenLoc(LParenLoc), RParenLoc(RParenLoc),
507-
NumQueries(queries.size()), AvailableRange(VersionRange::empty()),
508-
VariantAvailableRange(VersionRange::empty()), Flags() {
499+
NumQueries(queries.size()), Flags() {
509500
Flags.isInvalid = false;
510501
Flags.isUnavailability = isUnavailability;
511502
std::uninitialized_copy(queries.begin(), queries.end(),
@@ -539,16 +530,11 @@ class alignas(8) PoundAvailableInfo final :
539530
SourceRange getSourceRange() const { return SourceRange(getStartLoc(),
540531
getEndLoc()); }
541532

542-
std::optional<VersionRange> getAvailableRange() const {
543-
return AvailableRange;
544-
}
545-
void setAvailableRange(const VersionRange &Range) { AvailableRange = Range; }
546-
547-
std::optional<VersionRange> getVariantAvailableRange() const {
548-
return VariantAvailableRange;
533+
std::optional<const AvailabilityQuery> getAvailabilityQuery() const {
534+
return Query;
549535
}
550-
void setVariantAvailableRange(const VersionRange &Range) {
551-
VariantAvailableRange = Range;
536+
void setAvailabilityQuery(const AvailabilityQuery &query) {
537+
Query.emplace(query);
552538
}
553539

554540
bool isUnavailability() const { return Flags.isUnavailability; }

lib/AST/AvailabilityScopeBuilder.cpp

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,77 @@ class AvailabilityScopeBuilder : private ASTWalker {
824824
pushContext(fallthroughScope, parentBrace);
825825
}
826826

827+
AvailabilityQuery buildAvailabilityQuery(
828+
const SemanticAvailabilitySpec spec,
829+
const std::optional<SemanticAvailabilitySpec> &variantSpec,
830+
bool isUnavailability) {
831+
auto domain = spec.getDomain();
832+
833+
// Variant availability specfications are only supported for platform
834+
// domains when compiling with a -target-variant.
835+
if (!Context.LangOpts.TargetVariant)
836+
ASSERT(!variantSpec);
837+
838+
auto runtimeRangeForSpec =
839+
[](const std::optional<SemanticAvailabilitySpec> &spec)
840+
-> std::optional<AvailabilityRange> {
841+
if (!spec || spec->isWildcard() || !spec->getDomain().isVersioned())
842+
return std::nullopt;
843+
844+
return AvailabilityRange(spec->getRuntimeVersion());
845+
};
846+
847+
auto primaryRange = runtimeRangeForSpec(spec);
848+
auto variantRange = runtimeRangeForSpec(variantSpec);
849+
850+
switch (domain.getKind()) {
851+
case AvailabilityDomain::Kind::Embedded:
852+
case AvailabilityDomain::Kind::SwiftLanguage:
853+
case AvailabilityDomain::Kind::PackageDescription:
854+
// These domains don't support queries.
855+
llvm::report_fatal_error("unsupported domain");
856+
857+
case AvailabilityDomain::Kind::Universal:
858+
DEBUG_ASSERT(spec.isWildcard());
859+
860+
// If all of the specs that matched are '*', then the query trivially
861+
// evaluates to "true" at compile time.
862+
if (!variantRange)
863+
return AvailabilityQuery::constant(domain, isUnavailability, true);
864+
865+
// Otherwise, generate a dynamic query for the variant spec. For example,
866+
// when compiling zippered for macOS, this should generate a query that
867+
// just checks the iOS version at runtime:
868+
//
869+
// if #available(iOS 18, *) { ... }
870+
//
871+
return AvailabilityQuery::dynamic(variantSpec->getDomain(),
872+
isUnavailability, primaryRange,
873+
variantRange);
874+
875+
case AvailabilityDomain::Kind::Platform:
876+
// Platform checks are always dynamic. The SIL optimizer is responsible
877+
// eliminating these checks when it can prove that they can never fail
878+
// (due to the deployment target). We can't perform that analysis here
879+
// because it may depend on inlining.
880+
return AvailabilityQuery::dynamic(domain, isUnavailability, primaryRange,
881+
variantRange);
882+
case AvailabilityDomain::Kind::Custom:
883+
auto customDomain = domain.getCustomDomain();
884+
ASSERT(customDomain);
885+
886+
switch (customDomain->getKind()) {
887+
case CustomAvailabilityDomain::Kind::Enabled:
888+
return AvailabilityQuery::constant(domain, isUnavailability, true);
889+
case CustomAvailabilityDomain::Kind::Disabled:
890+
return AvailabilityQuery::constant(domain, isUnavailability, false);
891+
case CustomAvailabilityDomain::Kind::Dynamic:
892+
return AvailabilityQuery::dynamic(domain, isUnavailability,
893+
primaryRange, variantRange);
894+
}
895+
}
896+
}
897+
827898
/// Build the availability scopes for a StmtCondition and return a pair of
828899
/// optional availability contexts, the first for the true branch and the
829900
/// second for the false branch. A value of `nullopt` for a given branch
@@ -982,23 +1053,18 @@ class AvailabilityScopeBuilder : private ASTWalker {
9821053
continue;
9831054
}
9841055

985-
auto runtimeQueryRange = runtimeQueryRangeForSpec(*spec);
986-
query->setAvailableRange(runtimeQueryRange.getRawVersionRange());
987-
9881056
// When compiling zippered for macCatalyst, we need to collect both
9891057
// a macOS version (the target version) and an iOS/macCatalyst version
9901058
// (the target-variant). These versions will both be passed to a runtime
9911059
// entrypoint that will check either the macOS version or the iOS
9921060
// version depending on the kind of process this code is loaded into.
993-
if (Context.LangOpts.TargetVariant) {
994-
auto variantSpec =
995-
bestActiveSpecForQuery(query, /*ForTargetVariant*/ true);
996-
if (variantSpec) {
997-
auto variantQueryRange = runtimeQueryRangeForSpec(*variantSpec);
998-
query->setVariantAvailableRange(
999-
variantQueryRange.getRawVersionRange());
1000-
}
1001-
}
1061+
std::optional<SemanticAvailabilitySpec> variantSpec =
1062+
(Context.LangOpts.TargetVariant)
1063+
? bestActiveSpecForQuery(query, /*ForTargetVariant*/ true)
1064+
: std::nullopt;
1065+
1066+
query->setAvailabilityQuery(buildAvailabilityQuery(
1067+
*spec, variantSpec, query->isUnavailability()));
10021068

10031069
// Wildcards are expected to be "useless". There may be other specs in
10041070
// this query that are useful when compiling for other platforms.
@@ -1164,24 +1230,6 @@ class AvailabilityScopeBuilder : private ASTWalker {
11641230
}
11651231
}
11661232

1167-
/// Return the availability context for the given spec.
1168-
AvailabilityRange runtimeQueryRangeForSpec(SemanticAvailabilitySpec spec) {
1169-
if (spec.isWildcard())
1170-
return AvailabilityRange::alwaysAvailable();
1171-
1172-
auto domain = spec.getDomain();
1173-
if (domain.isVersioned())
1174-
return AvailabilityRange(spec.getRuntimeVersion());
1175-
1176-
// If it's not a versioned domain, we must be querying whether a domain is
1177-
// present or absent.
1178-
if (domain.isCustom() && domain.getCustomDomain()->getKind() ==
1179-
CustomAvailabilityDomain::Kind::Disabled)
1180-
return AvailabilityRange::neverAvailable();
1181-
1182-
return AvailabilityRange::alwaysAvailable();
1183-
}
1184-
11851233
/// For the given spec, returns a pair of availability ranges. The first range
11861234
/// is for the if/then flow and the second is for the if/else flow.
11871235
std::pair<std::optional<AvailabilityRange>, std::optional<AvailabilityRange>>

0 commit comments

Comments
 (0)