Skip to content

Commit 3843c7c

Browse files
committed
Update SWIFT_COMPILER_VERSION language features
The `SWIFT_COMPILER_VERSION` define is used to stamp a vendor’s version number into a Swift compiler binary. It can be queried from Swift code using `#if _compiler_version` and from Clang by using a preprocessor definition called `__SWIFT_COMPILER_VERSION`. These are unsupported compiler-internal features used primarily by Apple Swift. In Swift 1.0 through 5.5, Apple Swift used a scheme for `SWIFT_COMPILER_VERSION` where the major version matched the embedded clang (e.g. 1300 for Apple Clang 13.0.0) and the minor version was ignored. Starting in Swift 5.6, Apple Swift started using major and minor version numbers that matched the Swift.org version number. This makes them easier to understand, but it means that version 1300.0.x was followed by version 5.6.x. Not only did version numbers go backwards, but also the old logic to ignore minor versions was now a liability, because it meant you would not be able to target a change to 5.7.x compilers but not 5.6.x compilers. This commit addresses the problem by: * Modifying the existing `#if _compiler_version(string-literal)` feature so it transforms the major version into a major and minor that will compare correctly to new version numbers. For instance, “1300.*” is transformed into “1.300”, which will compare correctly to a “5.6” or “5.7” version even if it doesn’t really capture the fact that “1300” was a Swift 5.5 compiler. As a bonus, this allows you to use the feature to backwards-compatibly test new compilers using the existing feature: “5007.*” will be seen by compilers before 5.7 as an unknown future version, but will be seen by 5.7 compilers as targeting them. * Modifying the `__SWIFT_COMPILER_VERSION` clang define similarly so that, to preprocessor conditions written for the old scheme, a 5.7 compiler will appear to have major version 5007. * Adding a new variant of `#if _compiler_version` with the same syntax as `#if swift` and `#if compiler`—that is, taking a comparison operator and a bare set of dotted version numbers, rather than a string literal. Going forward, this will be how version checks are written once compatibility with compilers before this change is no longer a concern. These changes are only lightly tested because tests have to work without any compiler version defined (the default in most configurations), but I’ve tested what I can. Fixes rdar://89841295.
1 parent 255f965 commit 3843c7c

File tree

7 files changed

+142
-34
lines changed

7 files changed

+142
-34
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1819,7 +1819,10 @@ ERROR(version_component_not_number,none,
18191819
ERROR(compiler_version_too_many_components,none,
18201820
"compiler version must not have more than five components", ())
18211821
WARNING(unused_compiler_version_component,NoUsage,
1822-
"the second version component is not used for comparison", ())
1822+
"the second version component is not used for comparison in legacy "
1823+
"compiler versions%select{|; are you trying to encode a new Swift "
1824+
"compiler version for compatibility with legacy compilers?}0",
1825+
(bool))
18231826
ERROR(empty_version_component,none,
18241827
"found empty version component", ())
18251828
ERROR(compiler_version_component_out_of_range,none,

include/swift/Basic/Version.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,12 @@ class Version {
129129
/// Return this Version struct as the appropriate version string for APINotes.
130130
std::string asAPINotesVersionString() const;
131131

132-
/// Parse a version in the form used by the _compiler_version \#if condition.
132+
/// Parse a version in the form used by the _compiler_version(string-literal)
133+
/// \#if condition.
134+
///
135+
/// \note This is \em only used for the string literal version, so it includes
136+
/// backwards-compatibility logic to convert it to something that can be
137+
/// compared with a modern SWIFT_COMPILER_VERSION.
133138
static Optional<Version> parseCompilerVersionString(StringRef VersionString,
134139
SourceLoc Loc,
135140
DiagnosticEngine *Diags);

lib/Basic/Version.cpp

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "clang/Basic/CharInfo.h"
1818
#include "llvm/Support/raw_ostream.h"
19+
#include "llvm/Support/FormatVariadic.h"
1920
#include "llvm/ADT/SmallString.h"
2021
#include "llvm/ADT/StringExtras.h"
2122
#include "swift/AST/DiagnosticsParse.h"
@@ -132,9 +133,29 @@ Optional<Version> Version::parseCompilerVersionString(
132133
// The second version component isn't used for comparison.
133134
if (i == 1) {
134135
if (!SplitComponent.equals("*")) {
135-
if (Diags)
136-
Diags->diagnose(Range.Start, diag::unused_compiler_version_component)
137-
.fixItReplaceChars(Range.Start, Range.End, "*");
136+
if (Diags) {
137+
// Majors 600-1300 were used for Swift 1.0-5.5 (based on clang
138+
// versions), but then we reset the numbering based on Swift versions,
139+
// so 5.6 had major 5. We assume that majors below 600 use the new
140+
// scheme and equal/above it use the old scheme.
141+
bool firstComponentLooksNew = CV.Components[0] < 600;
142+
143+
auto diag = Diags->diagnose(Range.Start,
144+
diag::unused_compiler_version_component,
145+
firstComponentLooksNew);
146+
147+
if (firstComponentLooksNew &&
148+
!SplitComponent.getAsInteger(10, ComponentNumber)) {
149+
// Fix-it version like "5.7.1.2.3" to "5007.*.1.2.3".
150+
auto newDigits = llvm::formatv("{0}{1,0+3}.*", CV.Components[0],
151+
ComponentNumber).str();
152+
diag.fixItReplaceChars(SplitComponents[0].second.Start,
153+
Range.End, newDigits);
154+
}
155+
else {
156+
diag.fixItReplaceChars(Range.Start, Range.End, "*");
157+
}
158+
}
138159
}
139160

140161
CV.Components.push_back(0);
@@ -159,6 +180,38 @@ Optional<Version> Version::parseCompilerVersionString(
159180
isValidVersion = false;
160181
}
161182

183+
// In the beginning, '_compiler_version(string-literal)' was designed for a
184+
// different version scheme where the major was fairly large and the minor
185+
// was ignored; now we use one where the minor is significant and major and
186+
// minor match the Swift language version. See the comment above on
187+
// `firstComponentLooksNew` for details.
188+
//
189+
// However, we want the string literal variant of '_compiler_version' to
190+
// maintain source compatibility with old checks; that means checks for new
191+
// versions have to be written so that old compilers will think they represent
192+
// newer versions, while new compilers have to interpret old version number
193+
// strings in a way that will compare correctly to the new versions compiled
194+
// into them.
195+
//
196+
// To achieve this, modern compilers divide the major by 1000 and overwrite
197+
// the wildcard component with the remainder, effectively shifting the last
198+
// three digits of the major into the minor, before comparing it to the
199+
// compiler version:
200+
//
201+
// _compiler_version("5007.*.1.2.3") -> 5.7.1.2.3
202+
// _compiler_version("1300.*.1.2.3") -> 1.300.1.2.3 (smaller than 5.6)
203+
// _compiler_version( "600.*.1.2.3") -> 0.600.1.2.3 (smaller than 5.6)
204+
//
205+
// So if you want to specify a 5.7.z.a.b version, we ask users to either write
206+
// it as 5007.*.z.a.b, or to use the new '_compiler_version(>= version)'
207+
// syntax instead, which does not perform this conversion.
208+
if (!CV.Components.empty()) {
209+
if (CV.Components.size() == 1)
210+
CV.Components.push_back(0);
211+
CV.Components[1] = CV.Components[0] % 1000;
212+
CV.Components[0] = CV.Components[0] / 1000;
213+
}
214+
162215
return isValidVersion ? Optional<Version>(CV) : None;
163216
}
164217

lib/ClangImporter/ClangImporter.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,9 +649,15 @@ importer::getNormalInvocationArguments(
649649
// declarations.
650650
auto V = version::Version::getCurrentCompilerVersion();
651651
if (!V.empty()) {
652+
// Note: Prior to Swift 5.7, the "Y" version component was omitted and the
653+
// "X" component resided in its digits.
652654
invocationArgStrs.insert(invocationArgStrs.end(), {
653655
V.preprocessorDefinition("__SWIFT_COMPILER_VERSION",
654-
{1000000000, /*ignored*/ 0, 1000000, 1000, 1}),
656+
{1000000000000, // X
657+
1000000000, // Y
658+
1000000, // Z
659+
1000, // a
660+
1}), // b
655661
});
656662
}
657663
} else {

lib/Parse/ParseIfConfig.cpp

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -283,30 +283,26 @@ class ValidateIfConfigCondition :
283283
}
284284
// '_compiler_version' '(' string-literal ')'
285285
if (*KindName == "_compiler_version") {
286-
auto SLE = dyn_cast<StringLiteralExpr>(Arg);
287-
if (!SLE) {
288-
D.diagnose(Arg->getLoc(),
289-
diag::unsupported_platform_condition_argument,
290-
"string literal");
291-
return nullptr;
292-
}
293-
294-
auto ValStr = SLE->getValue();
295-
if (ValStr.empty()) {
296-
D.diagnose(SLE->getLoc(), diag::empty_version_string);
297-
return nullptr;
286+
if (auto SLE = dyn_cast<StringLiteralExpr>(Arg)) {
287+
auto ValStr = SLE->getValue();
288+
if (ValStr.empty()) {
289+
D.diagnose(SLE->getLoc(), diag::empty_version_string);
290+
return nullptr;
291+
}
292+
293+
auto Val = version::Version::parseCompilerVersionString(
294+
SLE->getValue(), SLE->getLoc(), &D);
295+
if (!Val.hasValue())
296+
return nullptr;
297+
return E;
298298
}
299-
300-
auto Val = version::Version::parseCompilerVersionString(
301-
SLE->getValue(), SLE->getLoc(), &D);
302-
if (!Val.hasValue())
303-
return nullptr;
304-
return E;
305299
}
306300

307301
// 'swift' '(' ('>=' | '<') float-literal ( '.' integer-literal )* ')'
308302
// 'compiler' '(' ('>=' | '<') float-literal ( '.' integer-literal )* ')'
309-
if (*KindName == "swift" || *KindName == "compiler") {
303+
// '_compiler_version' '(' ('>=' | '<') float-literal ( '.' integer-literal )* ')'
304+
if (*KindName == "swift" || *KindName == "compiler" ||
305+
*KindName == "_compiler_version") {
310306
auto PUE = dyn_cast<PrefixUnaryExpr>(Arg);
311307
Optional<StringRef> PrefixName =
312308
PUE ? getDeclRefStr(PUE->getFn(), DeclRefKind::PrefixOperator) : None;
@@ -500,28 +496,30 @@ class EvaluateIfConfigCondition :
500496
bool visitCallExpr(CallExpr *E) {
501497
auto KindName = getDeclRefStr(E->getFn());
502498
auto *Arg = getSingleSubExp(E->getArgs(), KindName, nullptr);
503-
if (KindName == "_compiler_version") {
499+
if (KindName == "_compiler_version" && isa<StringLiteralExpr>(Arg)) {
504500
auto Str = cast<StringLiteralExpr>(Arg)->getValue();
505501
auto Val = version::Version::parseCompilerVersionString(
506502
Str, SourceLoc(), nullptr).getValue();
507503
auto thisVersion = version::Version::getCurrentCompilerVersion();
508504
return thisVersion >= Val;
509-
} else if ((KindName == "swift") || (KindName == "compiler")) {
505+
} else if ((KindName == "swift") || (KindName == "compiler") ||
506+
(KindName == "_compiler_version")) {
510507
auto PUE = cast<PrefixUnaryExpr>(Arg);
511508
auto PrefixName = getDeclRefStr(PUE->getFn());
512509
auto Str = extractExprSource(Ctx.SourceMgr, PUE->getOperand());
513510
auto Val = version::Version::parseVersionString(
514511
Str, SourceLoc(), nullptr).getValue();
512+
version::Version thisVersion;
515513
if (KindName == "swift") {
516-
return isValidVersion(Ctx.LangOpts.EffectiveLanguageVersion, Val,
517-
PrefixName);
514+
thisVersion = Ctx.LangOpts.EffectiveLanguageVersion;
518515
} else if (KindName == "compiler") {
519-
auto currentLanguageVersion =
520-
version::Version::getCurrentLanguageVersion();
521-
return isValidVersion(currentLanguageVersion, Val, PrefixName);
516+
thisVersion = version::Version::getCurrentLanguageVersion();
517+
} else if (KindName == "_compiler_version") {
518+
thisVersion = version::Version::getCurrentCompilerVersion();
522519
} else {
523520
llvm_unreachable("unsupported version conditional");
524521
}
522+
return isValidVersion(thisVersion, Val, PrefixName);
525523
} else if (KindName == "canImport") {
526524
auto Str = extractExprSource(Ctx.SourceMgr, Arg);
527525
bool underlyingModule = false;

test/Parse/ConditionalCompilation/compiler_version.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
asdf asdf asdf asdf
88
#endif
99

10-
#if _compiler_version("10.*.10.10")
10+
#if _compiler_version("600.*.10.10")
1111

1212
#if os(iOS)
1313
let z = 1
@@ -48,7 +48,10 @@
4848
let thisWillStillParseBecauseConfigIsError = 1
4949
#endif
5050

51-
#if _compiler_version("700.0.100") // expected-warning {{the second version component is not used for comparison}}
51+
#if _compiler_version("700.0.100") // expected-warning {{the second version component is not used for comparison in legacy compiler versions}} {{28-29=*}}
52+
#endif
53+
54+
#if _compiler_version("5.7.100") // expected-warning {{the second version component is not used for comparison in legacy compiler versions; are you trying to encode a new Swift compiler version for compatibility with legacy compilers?}} {{24-27=5007.*}}
5255
#endif
5356

5457
#if _compiler_version("700.*.1.1.1.1") // expected-error {{version must not have more than five components}}
@@ -65,3 +68,14 @@
6568

6669
#if _compiler_version("700.*.1.1.1000") // expected-error {{version component out of range: must be in [0, 999]}}
6770
#endif
71+
72+
// New style _compiler_version()
73+
#if _compiler_version(<4.0)
74+
// This shouldn't emit any diagnostics.
75+
asdf asdf asdf asdf
76+
#endif
77+
78+
#if !_compiler_version(>=4.3.2.1.0)
79+
// This shouldn't emit any diagnostics.
80+
asdf asdf asdf asdf
81+
#endif

unittests/Parse/BuildConfigTests.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ using namespace llvm;
88

99
class CompilerVersionTest : public ::testing::Test {};
1010
class VersionTest : public ::testing::Test{};
11+
class CompilerVersionUnpackingTest : public ::testing::Test {};
1112

1213
Optional<version::Version> CV(const char *VersionString) {
1314
return version::Version::parseCompilerVersionString(VersionString,
@@ -52,3 +53,31 @@ TEST_F(VersionTest, VersionComparison) {
5253
EXPECT_FALSE(V(".1").hasValue());
5354

5455
}
56+
57+
TEST_F(CompilerVersionUnpackingTest, VersionComparison) {
58+
EXPECT_EQ(CV("700").getValue(), V("0.700").getValue());
59+
EXPECT_EQ(CV("700.*").getValue(), V("0.700").getValue());
60+
EXPECT_EQ(CV("700.*.1").getValue(), V("0.700.1").getValue());
61+
EXPECT_EQ(CV("700.*.23").getValue(), V("0.700.23").getValue());
62+
EXPECT_EQ(CV("700.*.1.1").getValue(), V("0.700.1.1").getValue());
63+
64+
EXPECT_EQ(CV("1300").getValue(), V("1.300").getValue());
65+
EXPECT_EQ(CV("1300.*").getValue(), V("1.300").getValue());
66+
EXPECT_EQ(CV("1300.*.1").getValue(), V("1.300.1").getValue());
67+
EXPECT_EQ(CV("1300.*.23").getValue(), V("1.300.23").getValue());
68+
EXPECT_EQ(CV("1300.*.1.1").getValue(), V("1.300.1.1").getValue());
69+
70+
EXPECT_EQ(CV("5007").getValue(), V("5.7").getValue());
71+
EXPECT_EQ(CV("5007.*").getValue(), V("5.7").getValue());
72+
EXPECT_EQ(CV("5007.*.1").getValue(), V("5.7.1").getValue());
73+
EXPECT_EQ(CV("5007.*.23").getValue(), V("5.7.23").getValue());
74+
EXPECT_EQ(CV("5007.*.1.1").getValue(), V("5.7.1.1").getValue());
75+
76+
// Since this test was added during 5.7, we expect all of these comparisons to
77+
// be GE, either because we are comparing to the empty version or because we
78+
// are comparing to a version >= 5.7.0.0.0.
79+
auto currentVersion = version::Version::getCurrentCompilerVersion();
80+
EXPECT_GE(CV("700"), currentVersion);
81+
EXPECT_GE(CV("1300"), currentVersion);
82+
EXPECT_GE(CV("5007"), currentVersion);
83+
}

0 commit comments

Comments
 (0)