Skip to content

Commit cad1ee7

Browse files
committed
AST: Optimize the layout of AvailabilityRange.
`AvailabilityRange` is now being used as a currency type in more of the compiler, and some of those uses are in permanent `ASTContext` allocations. The class wraps the `VersionRange` utility, which is itself a wrapper around `llvm::VersionTuple` with some additional storage for representing sentinel values. Even though the two sentinel values can be be represented with just a single bit of additional storage on top of the 16 bytes required to represent `VersionTuple`, because of alignment requirements the sentinel values end up bloating the layout of `VersionRange` by many bytes. To make `AvailabilityRange` and `VersionRange` more efficient to store, we can instead reserve two unlikely `llvm::VersionTuple` bit patterns as the sentinel values instead. The values chosen are the same ones LLVM uses to represent version tuple tombstones and empty keys in a `DenseMap`.
1 parent a870e25 commit cad1ee7

9 files changed

+144
-47
lines changed

include/swift/AST/ASTBridging.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ enum ENUM_EXTENSIBILITY_ATTR(open) BridgedDiagID : uint32_t {
514514
};
515515

516516
class BridgedDiagnosticArgument {
517-
int64_t storage[4];
517+
int64_t storage[3];
518518

519519
public:
520520
BRIDGED_INLINE BridgedDiagnosticArgument(const swift::DiagnosticArgument &arg);

include/swift/AST/AvailabilityRange.h

+29-33
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#ifndef SWIFT_AST_AVAILABILITY_RANGE_H
1818
#define SWIFT_AST_AVAILABILITY_RANGE_H
1919

20+
#include "swift/Basic/Assertions.h"
2021
#include "swift/Basic/LLVM.h"
2122
#include "llvm/ADT/FoldingSet.h"
2223
#include "llvm/Support/VersionTuple.h"
@@ -36,36 +37,39 @@ class VersionRange {
3637
// All: all versions
3738
// x.y.x: all versions greater than or equal to x.y.z
3839

39-
enum class ExtremalRange { Empty, All };
40+
/// The sentinel version tuple representing a range containing all versions.
41+
constexpr static llvm::VersionTuple getAllTuple() {
42+
return llvm::VersionTuple(0x7FFFFFFE);
43+
}
44+
45+
/// The sentinel version tuple representing an empty range.
46+
constexpr static llvm::VersionTuple getEmptyTuple() {
47+
return llvm::VersionTuple(0x7FFFFFFF);
48+
}
4049

4150
// A version range is either an extremal value (Empty, All) or
4251
// a single version tuple value representing the lower end point x.y.z of a
4352
// range [x.y.z, +Inf).
44-
union {
45-
llvm::VersionTuple LowerEndpoint;
46-
ExtremalRange ExtremalValue;
47-
};
48-
49-
unsigned HasLowerEndpoint : 1;
53+
llvm::VersionTuple LowerEndpoint;
5054

5155
public:
5256
/// Returns true if the range of versions is empty, or false otherwise.
5357
bool isEmpty() const {
54-
return !HasLowerEndpoint && ExtremalValue == ExtremalRange::Empty;
58+
return !hasLowerEndpoint() && LowerEndpoint == getEmptyTuple();
5559
}
5660

5761
/// Returns true if the range includes all versions, or false otherwise.
5862
bool isAll() const {
59-
return !HasLowerEndpoint && ExtremalValue == ExtremalRange::All;
63+
return !hasLowerEndpoint() && LowerEndpoint == getAllTuple();
6064
}
6165

6266
/// Returns true if the range has a lower end point; that is, if it is of
6367
/// the form [X, +Inf).
64-
bool hasLowerEndpoint() const { return HasLowerEndpoint; }
68+
bool hasLowerEndpoint() const { return isValidVersion(LowerEndpoint); }
6569

6670
/// Returns the range's lower endpoint.
6771
const llvm::VersionTuple &getLowerEndpoint() const {
68-
assert(HasLowerEndpoint);
72+
assert(hasLowerEndpoint());
6973
return LowerEndpoint;
7074
}
7175

@@ -124,7 +128,7 @@ class VersionRange {
124128
const llvm::VersionTuple maxVersion =
125129
std::max(this->getLowerEndpoint(), Other.getLowerEndpoint());
126130

127-
setLowerEndpoint(maxVersion);
131+
LowerEndpoint = maxVersion;
128132
}
129133

130134
/// Mutates this range to be the union of itself and Other. This is the
@@ -145,7 +149,7 @@ class VersionRange {
145149
const llvm::VersionTuple minVersion =
146150
std::min(this->getLowerEndpoint(), Other.getLowerEndpoint());
147151

148-
setLowerEndpoint(minVersion);
152+
LowerEndpoint = minVersion;
149153
}
150154

151155
/// Mutates this range to be a best effort over-approximation of the
@@ -160,37 +164,29 @@ class VersionRange {
160164
}
161165

162166
/// Returns a version range representing all versions.
163-
static VersionRange all() { return VersionRange(ExtremalRange::All); }
167+
static VersionRange all() { return VersionRange(getAllTuple()); }
164168

165169
/// Returns a version range representing no versions.
166-
static VersionRange empty() { return VersionRange(ExtremalRange::Empty); }
170+
static VersionRange empty() { return VersionRange(getEmptyTuple()); }
171+
172+
/// Returns false if the given version tuple cannot be used as a lower
173+
/// endpoint for `VersionRange`.
174+
static bool isValidVersion(const llvm::VersionTuple &EndPoint) {
175+
return EndPoint != getAllTuple() && EndPoint != getEmptyTuple();
176+
}
167177

168178
/// Returns a version range representing all versions greater than or equal
169179
/// to the passed-in version.
170180
static VersionRange allGTE(const llvm::VersionTuple &EndPoint) {
181+
ASSERT(isValidVersion(EndPoint));
171182
return VersionRange(EndPoint);
172183
}
173184

174185
void Profile(llvm::FoldingSetNodeID &ID) const;
175186

176187
private:
177-
VersionRange(const llvm::VersionTuple &LowerEndpoint) {
178-
setLowerEndpoint(LowerEndpoint);
179-
}
180-
181-
VersionRange(ExtremalRange ExtremalValue) {
182-
setExtremalRange(ExtremalValue);
183-
}
184-
185-
void setExtremalRange(ExtremalRange Version) {
186-
HasLowerEndpoint = 0;
187-
ExtremalValue = Version;
188-
}
189-
190-
void setLowerEndpoint(const llvm::VersionTuple &Version) {
191-
HasLowerEndpoint = 1;
192-
LowerEndpoint = Version;
193-
}
188+
VersionRange(const llvm::VersionTuple &LowerEndpoint)
189+
: LowerEndpoint(LowerEndpoint) {}
194190
};
195191

196192
/// Represents a version range in which something is available.
@@ -327,7 +323,7 @@ class AvailabilityRange {
327323
/// Returns a representation of the raw version range as a string for
328324
/// debugging purposes.
329325
std::string getVersionString() const {
330-
assert(Range.hasLowerEndpoint());
326+
ASSERT(Range.hasLowerEndpoint());
331327
return Range.getLowerEndpoint().getAsString();
332328
}
333329
};

include/swift/AST/DiagnosticsSema.def

+2
Original file line numberDiff line numberDiff line change
@@ -6754,6 +6754,8 @@ WARNING(availability_suggest_platform_name,
67546754
PointsToFirstBadToken, "unrecognized platform name %0;"
67556755
" did you mean '%1'?",
67566756
(Identifier, StringRef))
6757+
WARNING(availability_unsupported_version_number, none,
6758+
"'%0' is not a supported version number", (llvm::VersionTuple))
67576759

67586760
WARNING(attr_availability_expected_deprecated_version, none,
67596761
"expected version number with 'deprecated' in '%0' attribute for %1",

lib/AST/Availability.cpp

+23-5
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,25 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
769769
if (!domain)
770770
return std::nullopt;
771771

772-
auto semanticAttr = SemanticAvailableAttr(attr);
772+
auto checkVersion = [&](std::optional<llvm::VersionTuple> version,
773+
SourceRange sourceRange) {
774+
if (version && !VersionRange::isValidVersion(*version)) {
775+
diags
776+
.diagnose(attrLoc, diag::availability_unsupported_version_number,
777+
*version)
778+
.highlight(sourceRange);
779+
return true;
780+
}
781+
782+
return false;
783+
};
784+
785+
if (checkVersion(attr->getRawIntroduced(), attr->IntroducedRange))
786+
return std::nullopt;
787+
if (checkVersion(attr->getRawDeprecated(), attr->DeprecatedRange))
788+
return std::nullopt;
789+
if (checkVersion(attr->getRawObsoleted(), attr->ObsoletedRange))
790+
return std::nullopt;
773791

774792
bool hasIntroduced = attr->getRawIntroduced().has_value();
775793
bool hasDeprecated = attr->getRawDeprecated().has_value();
@@ -779,11 +797,11 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
779797
if (!domain->isVersioned() && hasVersionSpec) {
780798
SourceRange versionSourceRange;
781799
if (hasIntroduced)
782-
versionSourceRange = semanticAttr.getIntroducedSourceRange();
800+
versionSourceRange = attr->IntroducedRange;
783801
else if (hasDeprecated)
784-
versionSourceRange = semanticAttr.getDeprecatedSourceRange();
802+
versionSourceRange = attr->DeprecatedRange;
785803
else if (hasObsoleted)
786-
versionSourceRange = semanticAttr.getObsoletedSourceRange();
804+
versionSourceRange = attr->ObsoletedRange;
787805

788806
diags.diagnose(attrLoc, diag::availability_unexpected_version, *domain)
789807
.limitBehaviorIf(domain->isUniversal(), DiagnosticBehavior::Warning)
@@ -827,7 +845,7 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
827845
}
828846
}
829847

830-
return semanticAttr;
848+
return SemanticAvailableAttr(attr);
831849
}
832850

833851
std::optional<llvm::VersionTuple> SemanticAvailableAttr::getIntroduced() const {

lib/AST/AvailabilitySpec.cpp

+22-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#include "swift/AST/AvailabilitySpec.h"
1818
#include "swift/AST/ASTContext.h"
1919
#include "swift/AST/AvailabilityDomain.h"
20-
#include "swift/AST/DiagnosticsParse.h"
20+
#include "swift/AST/DiagnosticsSema.h"
2121
#include "swift/AST/TypeCheckRequests.h"
2222
#include "llvm/Support/raw_ostream.h"
2323

@@ -116,21 +116,35 @@ std::optional<SemanticAvailabilitySpec>
116116
SemanticAvailabilitySpecRequest::evaluate(
117117
Evaluator &evaluator, const AvailabilitySpec *spec,
118118
const DeclContext *declContext) const {
119+
if (spec->isInvalid())
120+
return std::nullopt;
121+
122+
auto &diags = declContext->getASTContext().Diags;
119123
AvailabilitySpec *mutableSpec = const_cast<AvailabilitySpec *>(spec);
120-
if (mutableSpec->resolveInDeclContext(declContext).has_value())
121-
return SemanticAvailabilitySpec(spec);
122-
return std::nullopt;
124+
if (!mutableSpec->resolveInDeclContext(declContext).has_value())
125+
return std::nullopt;
126+
127+
auto version = spec->getRawVersion();
128+
if (!VersionRange::isValidVersion(version)) {
129+
diags
130+
.diagnose(spec->getStartLoc(),
131+
diag::availability_unsupported_version_number, version)
132+
.highlight(spec->getVersionSrcRange());
133+
return std::nullopt;
134+
}
135+
136+
return SemanticAvailabilitySpec(spec);
123137
}
124138

125139
std::optional<std::optional<SemanticAvailabilitySpec>>
126140
SemanticAvailabilitySpecRequest::getCachedResult() const {
127141
auto *spec = std::get<0>(getStorage());
142+
if (spec->isInvalid())
143+
return std::optional<SemanticAvailabilitySpec>();
144+
128145
auto domainOrIdentifier = spec->getDomainOrIdentifier();
129146
if (!domainOrIdentifier.isResolved())
130-
return {};
131-
132-
if (spec->isInvalid())
133-
return {std::nullopt};
147+
return std::nullopt;
134148

135149
return SemanticAvailabilitySpec(spec);
136150
}

lib/Sema/MiscDiagnostics.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -5158,6 +5158,17 @@ static bool diagnoseAvailabilityCondition(PoundAvailableInfo *info,
51585158
return true;
51595159
}
51605160

5161+
if (hasVersion) {
5162+
auto rawVersion = parsedSpec->getRawVersion();
5163+
if (!VersionRange::isValidVersion(rawVersion)) {
5164+
diags
5165+
.diagnose(loc, diag::availability_unsupported_version_number,
5166+
rawVersion)
5167+
.highlight(parsedSpec->getVersionSrcRange());
5168+
return true;
5169+
}
5170+
}
5171+
51615172
if (domain.isVersioned()) {
51625173
if (!hasVersion) {
51635174
diags.diagnose(loc, diag::avail_query_expected_version_number);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// RUN: %target-typecheck-verify-swift
2+
3+
@available(macOS, introduced: 2147483646) // expected-warning {{'2147483646' is not a supported version number}}
4+
func funcIntroducedInMacOS2147483646() { }
5+
6+
@available(macOS 2147483646, *) // expected-warning {{'2147483646' is not a supported version number}}
7+
func funcIntroducedInMacOS2147483646Short() { }
8+
9+
@available(macOS, deprecated: 2147483646) // expected-warning {{'2147483646' is not a supported version number}}
10+
func funcDeprecatedInMacOS2147483646() { }
11+
12+
@available(macOS, obsoleted: 2147483646) // expected-warning {{'2147483646' is not a supported version number}}
13+
func funcObsoletedInMacOS2147483646() { }
14+
15+
@available(macOS, introduced: 2147483647) // expected-warning {{'2147483647' is not a supported version number}}
16+
func funcIntroducedInMacOS2147483647() { }
17+
18+
@available(macOS 2147483647, *) // expected-warning {{'2147483647' is not a supported version number}}
19+
func funcIntroducedInMacOS2147483647Short() { }
20+
21+
@available(macOS, deprecated: 2147483647) // expected-warning {{'2147483647' is not a supported version number}}
22+
func funcDeprecatedInMacOS2147483647() { }
23+
24+
@available(macOS, obsoleted: 2147483647) // expected-warning {{'2147483647' is not a supported version number}}
25+
func funcObsoletedInMacOS2147483647() { }
26+
27+
@available(swift, introduced: 2147483646) // expected-warning {{'2147483646' is not a supported version number}}
28+
func funcIntroducedInSwift2147483646() { }
29+
30+
func useExtremeVersions() {
31+
if #available(macOS 2147483646, *) { // expected-warning {{'2147483646' is not a supported version number}}
32+
funcIntroducedInMacOS2147483646()
33+
funcIntroducedInMacOS2147483646Short()
34+
funcDeprecatedInMacOS2147483646()
35+
funcObsoletedInMacOS2147483646()
36+
}
37+
if #available(macOS 2147483647, *) { // expected-warning {{'2147483647' is not a supported version number}}
38+
funcIntroducedInMacOS2147483647()
39+
funcIntroducedInMacOS2147483647Short()
40+
funcDeprecatedInMacOS2147483647()
41+
funcObsoletedInMacOS2147483647()
42+
}
43+
funcIntroducedInSwift2147483646()
44+
}

test/attr/attr_availability.swift

+6
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ let _: Int
155155
@available(OSX, introduced: 0.0.0) // expected-warning{{expected version number in 'available' attribute; this is an error in the Swift 6 language mode}}
156156
let _: Int
157157

158+
@available(OSX, introduced: 2147483646)
159+
let _: Int
160+
161+
@available(OSX, introduced: 2147483647)
162+
let _: Int
163+
158164
@available(*, renamed: "bad name") // expected-error{{'renamed' argument of 'available' attribute must be an operator, identifier, or full function name, optionally prefixed by a type name}}
159165
let _: Int
160166

unittests/AST/VersionRangeTests.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,9 @@ TEST_F(VersionRangeLattice, JoinWithClosedEndedPositiveInfinity) {
125125
EXPECT_TRUE(unionEquals(GreaterThanEqual10_10, GreaterThanEqual10_9,
126126
GreaterThanEqual10_9));
127127
}
128+
129+
TEST_F(VersionRangeLattice, ValidVersionTuples) {
130+
EXPECT_TRUE(VersionRange::isValidVersion(llvm::VersionTuple()));
131+
EXPECT_FALSE(VersionRange::isValidVersion(llvm::VersionTuple(0x7FFFFFFE)));
132+
EXPECT_FALSE(VersionRange::isValidVersion(llvm::VersionTuple(0x7FFFFFFF)));
133+
}

0 commit comments

Comments
 (0)