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`.
This commit is contained in:
Allan Shortlidge
2025-03-04 21:03:32 -08:00
parent a870e25c0c
commit cad1ee75d5
9 changed files with 144 additions and 47 deletions

View File

@@ -514,7 +514,7 @@ enum ENUM_EXTENSIBILITY_ATTR(open) BridgedDiagID : uint32_t {
};
class BridgedDiagnosticArgument {
int64_t storage[4];
int64_t storage[3];
public:
BRIDGED_INLINE BridgedDiagnosticArgument(const swift::DiagnosticArgument &arg);

View File

@@ -17,6 +17,7 @@
#ifndef SWIFT_AST_AVAILABILITY_RANGE_H
#define SWIFT_AST_AVAILABILITY_RANGE_H
#include "swift/Basic/Assertions.h"
#include "swift/Basic/LLVM.h"
#include "llvm/ADT/FoldingSet.h"
#include "llvm/Support/VersionTuple.h"
@@ -36,36 +37,39 @@ class VersionRange {
// All: all versions
// x.y.x: all versions greater than or equal to x.y.z
enum class ExtremalRange { Empty, All };
/// The sentinel version tuple representing a range containing all versions.
constexpr static llvm::VersionTuple getAllTuple() {
return llvm::VersionTuple(0x7FFFFFFE);
}
/// The sentinel version tuple representing an empty range.
constexpr static llvm::VersionTuple getEmptyTuple() {
return llvm::VersionTuple(0x7FFFFFFF);
}
// A version range is either an extremal value (Empty, All) or
// a single version tuple value representing the lower end point x.y.z of a
// range [x.y.z, +Inf).
union {
llvm::VersionTuple LowerEndpoint;
ExtremalRange ExtremalValue;
};
unsigned HasLowerEndpoint : 1;
llvm::VersionTuple LowerEndpoint;
public:
/// Returns true if the range of versions is empty, or false otherwise.
bool isEmpty() const {
return !HasLowerEndpoint && ExtremalValue == ExtremalRange::Empty;
return !hasLowerEndpoint() && LowerEndpoint == getEmptyTuple();
}
/// Returns true if the range includes all versions, or false otherwise.
bool isAll() const {
return !HasLowerEndpoint && ExtremalValue == ExtremalRange::All;
return !hasLowerEndpoint() && LowerEndpoint == getAllTuple();
}
/// Returns true if the range has a lower end point; that is, if it is of
/// the form [X, +Inf).
bool hasLowerEndpoint() const { return HasLowerEndpoint; }
bool hasLowerEndpoint() const { return isValidVersion(LowerEndpoint); }
/// Returns the range's lower endpoint.
const llvm::VersionTuple &getLowerEndpoint() const {
assert(HasLowerEndpoint);
assert(hasLowerEndpoint());
return LowerEndpoint;
}
@@ -124,7 +128,7 @@ public:
const llvm::VersionTuple maxVersion =
std::max(this->getLowerEndpoint(), Other.getLowerEndpoint());
setLowerEndpoint(maxVersion);
LowerEndpoint = maxVersion;
}
/// Mutates this range to be the union of itself and Other. This is the
@@ -145,7 +149,7 @@ public:
const llvm::VersionTuple minVersion =
std::min(this->getLowerEndpoint(), Other.getLowerEndpoint());
setLowerEndpoint(minVersion);
LowerEndpoint = minVersion;
}
/// Mutates this range to be a best effort over-approximation of the
@@ -160,37 +164,29 @@ public:
}
/// Returns a version range representing all versions.
static VersionRange all() { return VersionRange(ExtremalRange::All); }
static VersionRange all() { return VersionRange(getAllTuple()); }
/// Returns a version range representing no versions.
static VersionRange empty() { return VersionRange(ExtremalRange::Empty); }
static VersionRange empty() { return VersionRange(getEmptyTuple()); }
/// Returns false if the given version tuple cannot be used as a lower
/// endpoint for `VersionRange`.
static bool isValidVersion(const llvm::VersionTuple &EndPoint) {
return EndPoint != getAllTuple() && EndPoint != getEmptyTuple();
}
/// Returns a version range representing all versions greater than or equal
/// to the passed-in version.
static VersionRange allGTE(const llvm::VersionTuple &EndPoint) {
ASSERT(isValidVersion(EndPoint));
return VersionRange(EndPoint);
}
void Profile(llvm::FoldingSetNodeID &ID) const;
private:
VersionRange(const llvm::VersionTuple &LowerEndpoint) {
setLowerEndpoint(LowerEndpoint);
}
VersionRange(ExtremalRange ExtremalValue) {
setExtremalRange(ExtremalValue);
}
void setExtremalRange(ExtremalRange Version) {
HasLowerEndpoint = 0;
ExtremalValue = Version;
}
void setLowerEndpoint(const llvm::VersionTuple &Version) {
HasLowerEndpoint = 1;
LowerEndpoint = Version;
}
VersionRange(const llvm::VersionTuple &LowerEndpoint)
: LowerEndpoint(LowerEndpoint) {}
};
/// Represents a version range in which something is available.
@@ -327,7 +323,7 @@ public:
/// Returns a representation of the raw version range as a string for
/// debugging purposes.
std::string getVersionString() const {
assert(Range.hasLowerEndpoint());
ASSERT(Range.hasLowerEndpoint());
return Range.getLowerEndpoint().getAsString();
}
};

View File

@@ -6754,6 +6754,8 @@ WARNING(availability_suggest_platform_name,
PointsToFirstBadToken, "unrecognized platform name %0;"
" did you mean '%1'?",
(Identifier, StringRef))
WARNING(availability_unsupported_version_number, none,
"'%0' is not a supported version number", (llvm::VersionTuple))
WARNING(attr_availability_expected_deprecated_version, none,
"expected version number with 'deprecated' in '%0' attribute for %1",

View File

@@ -769,7 +769,25 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
if (!domain)
return std::nullopt;
auto semanticAttr = SemanticAvailableAttr(attr);
auto checkVersion = [&](std::optional<llvm::VersionTuple> version,
SourceRange sourceRange) {
if (version && !VersionRange::isValidVersion(*version)) {
diags
.diagnose(attrLoc, diag::availability_unsupported_version_number,
*version)
.highlight(sourceRange);
return true;
}
return false;
};
if (checkVersion(attr->getRawIntroduced(), attr->IntroducedRange))
return std::nullopt;
if (checkVersion(attr->getRawDeprecated(), attr->DeprecatedRange))
return std::nullopt;
if (checkVersion(attr->getRawObsoleted(), attr->ObsoletedRange))
return std::nullopt;
bool hasIntroduced = attr->getRawIntroduced().has_value();
bool hasDeprecated = attr->getRawDeprecated().has_value();
@@ -779,11 +797,11 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
if (!domain->isVersioned() && hasVersionSpec) {
SourceRange versionSourceRange;
if (hasIntroduced)
versionSourceRange = semanticAttr.getIntroducedSourceRange();
versionSourceRange = attr->IntroducedRange;
else if (hasDeprecated)
versionSourceRange = semanticAttr.getDeprecatedSourceRange();
versionSourceRange = attr->DeprecatedRange;
else if (hasObsoleted)
versionSourceRange = semanticAttr.getObsoletedSourceRange();
versionSourceRange = attr->ObsoletedRange;
diags.diagnose(attrLoc, diag::availability_unexpected_version, *domain)
.limitBehaviorIf(domain->isUniversal(), DiagnosticBehavior::Warning)
@@ -827,7 +845,7 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
}
}
return semanticAttr;
return SemanticAvailableAttr(attr);
}
std::optional<llvm::VersionTuple> SemanticAvailableAttr::getIntroduced() const {

View File

@@ -17,7 +17,7 @@
#include "swift/AST/AvailabilitySpec.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/AvailabilityDomain.h"
#include "swift/AST/DiagnosticsParse.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/TypeCheckRequests.h"
#include "llvm/Support/raw_ostream.h"
@@ -116,21 +116,35 @@ std::optional<SemanticAvailabilitySpec>
SemanticAvailabilitySpecRequest::evaluate(
Evaluator &evaluator, const AvailabilitySpec *spec,
const DeclContext *declContext) const {
if (spec->isInvalid())
return std::nullopt;
auto &diags = declContext->getASTContext().Diags;
AvailabilitySpec *mutableSpec = const_cast<AvailabilitySpec *>(spec);
if (mutableSpec->resolveInDeclContext(declContext).has_value())
return SemanticAvailabilitySpec(spec);
return std::nullopt;
if (!mutableSpec->resolveInDeclContext(declContext).has_value())
return std::nullopt;
auto version = spec->getRawVersion();
if (!VersionRange::isValidVersion(version)) {
diags
.diagnose(spec->getStartLoc(),
diag::availability_unsupported_version_number, version)
.highlight(spec->getVersionSrcRange());
return std::nullopt;
}
return SemanticAvailabilitySpec(spec);
}
std::optional<std::optional<SemanticAvailabilitySpec>>
SemanticAvailabilitySpecRequest::getCachedResult() const {
auto *spec = std::get<0>(getStorage());
if (spec->isInvalid())
return std::optional<SemanticAvailabilitySpec>();
auto domainOrIdentifier = spec->getDomainOrIdentifier();
if (!domainOrIdentifier.isResolved())
return {};
if (spec->isInvalid())
return {std::nullopt};
return std::nullopt;
return SemanticAvailabilitySpec(spec);
}

View File

@@ -5158,6 +5158,17 @@ static bool diagnoseAvailabilityCondition(PoundAvailableInfo *info,
return true;
}
if (hasVersion) {
auto rawVersion = parsedSpec->getRawVersion();
if (!VersionRange::isValidVersion(rawVersion)) {
diags
.diagnose(loc, diag::availability_unsupported_version_number,
rawVersion)
.highlight(parsedSpec->getVersionSrcRange());
return true;
}
}
if (domain.isVersioned()) {
if (!hasVersion) {
diags.diagnose(loc, diag::avail_query_expected_version_number);

View File

@@ -0,0 +1,44 @@
// RUN: %target-typecheck-verify-swift
@available(macOS, introduced: 2147483646) // expected-warning {{'2147483646' is not a supported version number}}
func funcIntroducedInMacOS2147483646() { }
@available(macOS 2147483646, *) // expected-warning {{'2147483646' is not a supported version number}}
func funcIntroducedInMacOS2147483646Short() { }
@available(macOS, deprecated: 2147483646) // expected-warning {{'2147483646' is not a supported version number}}
func funcDeprecatedInMacOS2147483646() { }
@available(macOS, obsoleted: 2147483646) // expected-warning {{'2147483646' is not a supported version number}}
func funcObsoletedInMacOS2147483646() { }
@available(macOS, introduced: 2147483647) // expected-warning {{'2147483647' is not a supported version number}}
func funcIntroducedInMacOS2147483647() { }
@available(macOS 2147483647, *) // expected-warning {{'2147483647' is not a supported version number}}
func funcIntroducedInMacOS2147483647Short() { }
@available(macOS, deprecated: 2147483647) // expected-warning {{'2147483647' is not a supported version number}}
func funcDeprecatedInMacOS2147483647() { }
@available(macOS, obsoleted: 2147483647) // expected-warning {{'2147483647' is not a supported version number}}
func funcObsoletedInMacOS2147483647() { }
@available(swift, introduced: 2147483646) // expected-warning {{'2147483646' is not a supported version number}}
func funcIntroducedInSwift2147483646() { }
func useExtremeVersions() {
if #available(macOS 2147483646, *) { // expected-warning {{'2147483646' is not a supported version number}}
funcIntroducedInMacOS2147483646()
funcIntroducedInMacOS2147483646Short()
funcDeprecatedInMacOS2147483646()
funcObsoletedInMacOS2147483646()
}
if #available(macOS 2147483647, *) { // expected-warning {{'2147483647' is not a supported version number}}
funcIntroducedInMacOS2147483647()
funcIntroducedInMacOS2147483647Short()
funcDeprecatedInMacOS2147483647()
funcObsoletedInMacOS2147483647()
}
funcIntroducedInSwift2147483646()
}

View File

@@ -155,6 +155,12 @@ let _: Int
@available(OSX, introduced: 0.0.0) // expected-warning{{expected version number in 'available' attribute; this is an error in the Swift 6 language mode}}
let _: Int
@available(OSX, introduced: 2147483646)
let _: Int
@available(OSX, introduced: 2147483647)
let _: Int
@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}}
let _: Int

View File

@@ -125,3 +125,9 @@ TEST_F(VersionRangeLattice, JoinWithClosedEndedPositiveInfinity) {
EXPECT_TRUE(unionEquals(GreaterThanEqual10_10, GreaterThanEqual10_9,
GreaterThanEqual10_9));
}
TEST_F(VersionRangeLattice, ValidVersionTuples) {
EXPECT_TRUE(VersionRange::isValidVersion(llvm::VersionTuple()));
EXPECT_FALSE(VersionRange::isValidVersion(llvm::VersionTuple(0x7FFFFFFE)));
EXPECT_FALSE(VersionRange::isValidVersion(llvm::VersionTuple(0x7FFFFFFF)));
}