Skip to content

Commit 6fb6a56

Browse files
committed
SILGen: Introduce macCatalyst support for if #available.
Upstream the necessary changes to compile `if #available` queries correctly when a `macabi` target triple or a `-target-variant` is specified.
1 parent e4331af commit 6fb6a56

13 files changed

+567
-7
lines changed

include/swift/AST/ASTContext.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,13 @@ class ASTContext final {
734734
// Retrieve the declaration of Swift._stdlib_isOSVersionAtLeast.
735735
FuncDecl *getIsOSVersionAtLeastDecl() const;
736736

737+
// Retrieve the declaration of Swift._stdlib_isVariantOSVersionAtLeast.
738+
FuncDecl *getIsVariantOSVersionAtLeastDecl() const;
739+
740+
// Retrieve the declaration of
741+
// Swift._stdlib_isOSVersionAtLeastOrVariantVersionAtLeast.
742+
FuncDecl *getIsOSVersionAtLeastOrVariantVersionAtLeast() const;
743+
737744
/// Look for the declaration with the given name within the
738745
/// passed in module.
739746
void lookupInModule(ModuleDecl *M, StringRef name,

lib/AST/ASTContext.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,23 @@ struct ASTContext::Implementation {
389389
/// -> Builtin.Int1
390390
FuncDecl *IsOSVersionAtLeastDecl = nullptr;
391391

392+
/// func _stdlib_isVariantOSVersionAtLeast(
393+
/// Builtin.Word,
394+
/// Builtin.Word,
395+
/// Builtin.word)
396+
/// -> Builtin.Int1
397+
FuncDecl *IsVariantOSVersionAtLeastDecl = nullptr;
398+
399+
/// func _stdlib_isOSVersionAtLeastOrVariantVersionAtLeast(
400+
/// Builtin.Word,
401+
/// Builtin.Word,
402+
/// Builtin.Word,
403+
/// Builtin.Word,
404+
/// Builtin.Word,
405+
/// Builtin.Word)
406+
/// -> Builtin.Int1
407+
FuncDecl *IsOSVersionAtLeastOrVariantVersionAtLeastDecl = nullptr;
408+
392409
/// The set of known protocols, lazily populated as needed.
393410
ProtocolDecl *KnownProtocols[NumKnownProtocols] = { };
394411

@@ -1842,6 +1859,31 @@ FuncDecl *ASTContext::getIsOSVersionAtLeastDecl() const {
18421859
return decl;
18431860
}
18441861

1862+
FuncDecl *ASTContext::getIsVariantOSVersionAtLeastDecl() const {
1863+
if (getImpl().IsVariantOSVersionAtLeastDecl)
1864+
return getImpl().IsVariantOSVersionAtLeastDecl;
1865+
1866+
auto decl = findLibraryIntrinsic(*this, "_stdlib_isVariantOSVersionAtLeast");
1867+
if (!decl)
1868+
return nullptr;
1869+
1870+
getImpl().IsVariantOSVersionAtLeastDecl = decl;
1871+
return decl;
1872+
}
1873+
1874+
FuncDecl *ASTContext::getIsOSVersionAtLeastOrVariantVersionAtLeast() const {
1875+
if (getImpl().IsOSVersionAtLeastOrVariantVersionAtLeastDecl)
1876+
return getImpl().IsOSVersionAtLeastOrVariantVersionAtLeastDecl;
1877+
1878+
auto decl = findLibraryIntrinsic(*this,
1879+
"_stdlib_isOSVersionAtLeastOrVariantVersionAtLeast");
1880+
if (!decl)
1881+
return nullptr;
1882+
1883+
getImpl().IsOSVersionAtLeastOrVariantVersionAtLeastDecl = decl;
1884+
return decl;
1885+
}
1886+
18451887
static bool isHigherPrecedenceThan(PrecedenceGroupDecl *a,
18461888
PrecedenceGroupDecl *b) {
18471889
assert(a != b && "exact match should already have been filtered");

lib/SILGen/SILGenDecl.cpp

Lines changed: 156 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "swift/AST/NameLookup.h"
2525
#include "swift/AST/PropertyWrappers.h"
2626
#include "swift/AST/ProtocolConformance.h"
27+
#include "swift/Basic/Platform.h"
2728
#include "swift/Basic/Assertions.h"
2829
#include "swift/Basic/ProfileCounter.h"
2930
#include "swift/SIL/FormalLinkage.h"
@@ -1725,17 +1726,26 @@ emitVersionLiterals(SILLocation loc, SILGenBuilder &B, ASTContext &ctx,
17251726
/// the specified version range and 0 otherwise. The returned SILValue
17261727
/// (which has type Builtin.Int1) represents the result of this check.
17271728
SILValue SILGenFunction::emitOSVersionRangeCheck(SILLocation loc,
1728-
const VersionRange &range) {
1729+
const VersionRange &range,
1730+
bool forTargetVariant) {
17291731
// Emit constants for the checked version range.
17301732
SILValue majorValue;
17311733
SILValue minorValue;
17321734
SILValue subminorValue;
1735+
17331736
std::tie(majorValue, minorValue, subminorValue) =
17341737
emitVersionLiterals(loc, B, getASTContext(), range.getLowerEndpoint());
17351738

17361739
// Emit call to _stdlib_isOSVersionAtLeast(major, minor, patch)
17371740
FuncDecl *versionQueryDecl =
17381741
getASTContext().getIsOSVersionAtLeastDecl();
1742+
1743+
// When targeting macCatalyst, the version number will be an iOS version number
1744+
// and so we call a variant of the query function that understands iOS
1745+
// versions.
1746+
if (forTargetVariant)
1747+
versionQueryDecl = getASTContext().getIsVariantOSVersionAtLeastDecl();
1748+
17391749
assert(versionQueryDecl);
17401750

17411751
auto silDeclRef = SILDeclRef(versionQueryDecl);
@@ -1746,6 +1756,130 @@ SILValue SILGenFunction::emitOSVersionRangeCheck(SILLocation loc,
17461756
return B.createApply(loc, availabilityGTEFn, SubstitutionMap(), args);
17471757
}
17481758

1759+
SILValue SILGenFunction::emitOSVersionOrVariantVersionRangeCheck(
1760+
SILLocation loc, const VersionRange &targetRange,
1761+
const VersionRange &variantRange) {
1762+
SILValue targetMajorValue;
1763+
SILValue targetMinorValue;
1764+
SILValue targetSubminorValue;
1765+
1766+
const llvm::VersionTuple &targetVersion = targetRange.getLowerEndpoint();
1767+
std::tie(targetMajorValue, targetMinorValue, targetSubminorValue) =
1768+
emitVersionLiterals(loc, B, getASTContext(), targetVersion);
1769+
1770+
SILValue variantMajorValue;
1771+
SILValue variantMinorValue;
1772+
SILValue variantSubminorValue;
1773+
1774+
const llvm::VersionTuple &variantVersion = variantRange.getLowerEndpoint();
1775+
std::tie(variantMajorValue, variantMinorValue, variantSubminorValue) =
1776+
emitVersionLiterals(loc, B, getASTContext(), variantVersion);
1777+
1778+
FuncDecl *versionQueryDecl =
1779+
getASTContext().getIsOSVersionAtLeastOrVariantVersionAtLeast();
1780+
1781+
assert(versionQueryDecl);
1782+
1783+
auto silDeclRef = SILDeclRef(versionQueryDecl);
1784+
SILValue availabilityGTEFn = emitGlobalFunctionRef(
1785+
loc, silDeclRef, getConstantInfo(getTypeExpansionContext(), silDeclRef));
1786+
1787+
SILValue args[] = {
1788+
targetMajorValue,
1789+
targetMinorValue,
1790+
targetSubminorValue,
1791+
variantMajorValue,
1792+
variantMinorValue,
1793+
variantSubminorValue
1794+
};
1795+
return B.createApply(loc, availabilityGTEFn, SubstitutionMap(), args);
1796+
}
1797+
1798+
SILValue SILGenFunction::emitZipperedOSVersionRangeCheck(
1799+
SILLocation loc, const VersionRange &targetRange,
1800+
const VersionRange &variantRange) {
1801+
assert(getASTContext().LangOpts.TargetVariant);
1802+
1803+
VersionRange OSVersion = targetRange;
1804+
VersionRange VariantOSVersion = variantRange;
1805+
1806+
// We're building zippered, so we need to pass both macOS and iOS
1807+
// versions to the the runtime version range check. At run time
1808+
// that check will determine what kind of process this code is loaded
1809+
// into. In a macOS process it will use the macOS version; in an
1810+
// macCatalyst process it will use the iOS version.
1811+
llvm::Triple VariantTriple = *getASTContext().LangOpts.TargetVariant;
1812+
llvm::Triple TargetTriple = getASTContext().LangOpts.Target;
1813+
assert(triplesAreValidForZippering(TargetTriple, VariantTriple));
1814+
1815+
// From perspective of the driver and most of the frontend,
1816+
// -target and -target-variant are symmetric. That is, the user
1817+
// can pass either:
1818+
// -target x86_64-apple-macosx10.15 \
1819+
// -target-variant x86_64-apple-ios13.1-macabi
1820+
// or:
1821+
// -target x86_64-apple-ios13.1-macabi \
1822+
// -target-variant x86_64-apple-macosx10.15
1823+
//
1824+
// However, the runtime availability-checking entry points need
1825+
// to compare against an actual running OS version and so can't be
1826+
// symmetric. Here we standardize on "target" means macOS version
1827+
// and "targetVariant" means iOS version.
1828+
if (tripleIsMacCatalystEnvironment(TargetTriple)) {
1829+
assert(VariantTriple.isMacOSX());
1830+
// Normalize so that "variant" always means iOS version.
1831+
std::swap(OSVersion, VariantOSVersion);
1832+
std::swap(TargetTriple, VariantTriple);
1833+
}
1834+
1835+
// If there is no check for either the target platform
1836+
// or the target-variant platform then the condition is
1837+
// trivially true.
1838+
if (OSVersion.isAll() && VariantOSVersion.isAll()) {
1839+
SILType i1 = SILType::getBuiltinIntegerType(1, getASTContext());
1840+
return B.createIntegerLiteral(loc, i1, true);
1841+
}
1842+
1843+
// The variant-only availability-checking entrypoint is not part
1844+
// of the Swift 5.0 ABI. It is only available in macOS 10.15 and above.
1845+
bool isVariantEntrypointAvailable = !TargetTriple.isMacOSXVersionLT(10, 15);
1846+
1847+
// If there is no check for the target but there is for the
1848+
// variant, then we only need to emit code for the variant check.
1849+
if (isVariantEntrypointAvailable && OSVersion.isAll() &&
1850+
!VariantOSVersion.isAll())
1851+
return emitOSVersionRangeCheck(loc, VariantOSVersion,
1852+
/*forVariant*/ true);
1853+
1854+
// Similarly, if there is a check for the target but not for the
1855+
// target variant then we only to emit code for the target check.
1856+
if (!OSVersion.isAll() && VariantOSVersion.isAll())
1857+
return emitOSVersionRangeCheck(loc, OSVersion,
1858+
/*forVariant*/ false);
1859+
1860+
if (!isVariantEntrypointAvailable ||
1861+
(!OSVersion.isAll() && !VariantOSVersion.isAll())) {
1862+
1863+
// If the variant-only entrypoint isn't available (as is the
1864+
// case pre-macOS 10.15) we need to use the zippered entrypoint
1865+
// (which is part of the Swift 5.0 ABI) even when the macOS version
1866+
// is '*' (all).
1867+
// In this case, use the minimum macOS deployment version from
1868+
// the target triple. This ensures the check always passes on macOS.
1869+
if (!isVariantEntrypointAvailable && OSVersion.isAll()) {
1870+
assert(TargetTriple.isMacOSX());
1871+
1872+
llvm::VersionTuple macosVersion;
1873+
TargetTriple.getMacOSXVersion(macosVersion);
1874+
OSVersion = VersionRange::allGTE(macosVersion);
1875+
}
1876+
1877+
return emitOSVersionOrVariantVersionRangeCheck(loc, OSVersion,
1878+
VariantOSVersion);
1879+
}
1880+
1881+
llvm_unreachable("Unhandled zippered configuration");
1882+
}
17491883

17501884
/// Emit the boolean test and/or pattern bindings indicated by the specified
17511885
/// stmt condition. If the condition fails, control flow is transferred to the
@@ -1795,20 +1929,38 @@ void SILGenFunction::emitStmtCondition(StmtCondition Cond, JumpDest FalseDest,
17951929
// specified by elt.
17961930
PoundAvailableInfo *availability = elt.getAvailability();
17971931
VersionRange OSVersion = availability->getAvailableRange();
1798-
1932+
17991933
// The OS version might be left empty if availability checking was
18001934
// disabled. Treat it as always-true in that case.
18011935
assert(!OSVersion.isEmpty()
18021936
|| getASTContext().LangOpts.DisableAvailabilityChecking);
1803-
1937+
1938+
if (getASTContext().LangOpts.TargetVariant) {
1939+
// We're building zippered, so we need to pass both macOS and iOS
1940+
// versions to the the runtime version range check. At run time
1941+
// that check will determine what kind of process this code is loaded
1942+
// into. In a macOS process it will use the macOS version; in an
1943+
// macCatalyst process it will use the iOS version.
1944+
1945+
VersionRange VariantOSVersion =
1946+
elt.getAvailability()->getVariantAvailableRange();
1947+
assert(!VariantOSVersion.isEmpty());
1948+
booleanTestValue =
1949+
emitZipperedOSVersionRangeCheck(loc, OSVersion, VariantOSVersion);
1950+
break;
1951+
}
1952+
18041953
if (OSVersion.isEmpty() || OSVersion.isAll()) {
18051954
// If there's no check for the current platform, this condition is
18061955
// trivially true (or false, for unavailability).
18071956
SILType i1 = SILType::getBuiltinIntegerType(1, getASTContext());
18081957
bool value = !availability->isUnavailability();
18091958
booleanTestValue = B.createIntegerLiteral(loc, i1, value);
18101959
} else {
1811-
booleanTestValue = emitOSVersionRangeCheck(loc, OSVersion);
1960+
bool isMacCatalyst =
1961+
tripleIsMacCatalystEnvironment(getASTContext().LangOpts.Target);
1962+
booleanTestValue = emitOSVersionRangeCheck(loc, OSVersion,
1963+
isMacCatalyst);
18121964
if (availability->isUnavailability()) {
18131965
// If this is an unavailability check, invert the result
18141966
// by emitting a call to Builtin.xor_Int1(lhs, -1).

lib/SILGen/SILGenFunction.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1591,7 +1591,19 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
15911591
// Patterns
15921592
//===--------------------------------------------------------------------===//
15931593

1594-
SILValue emitOSVersionRangeCheck(SILLocation loc, const VersionRange &range);
1594+
SILValue emitOSVersionRangeCheck(SILLocation loc, const VersionRange &range,
1595+
bool forTargetVariant = false);
1596+
SILValue
1597+
emitOSVersionOrVariantVersionRangeCheck(SILLocation loc,
1598+
const VersionRange &targetRange,
1599+
const VersionRange &variantRange);
1600+
/// Emits either a single OS version range check or an OS version & variant
1601+
/// version range check automatically, depending on the active target triple
1602+
/// and requested versions.
1603+
SILValue emitZipperedOSVersionRangeCheck(SILLocation loc,
1604+
const VersionRange &targetRange,
1605+
const VersionRange &variantRange);
1606+
15951607
void emitStmtCondition(StmtCondition Cond, JumpDest FalseDest, SILLocation loc,
15961608
ProfileCounter NumTrueTaken = ProfileCounter(),
15971609
ProfileCounter NumFalseTaken = ProfileCounter());

stdlib/public/core/Availability.swift

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,60 @@ public func _stdlib_isOSVersionAtLeast(
4444
#endif
4545
}
4646

47-
#if os(macOS) && SWIFT_RUNTIME_OS_VERSIONING
47+
// Performs an availability check in macCatalyst code to support back
48+
// deployment. This entry point takes in a variant OS version
49+
// (i.e, an iOS version).
50+
//
51+
// This is not inlinable because we
52+
// don't want to inline the messy implementation details of the
53+
// compiler-rt support into apps and expose those as ABI surface.
54+
//
4855
// This is a magic entry point known to the compiler. It is called in
4956
// generated code for API availability checking.
57+
58+
#if (os(macOS) || os(iOS) && targetEnvironment(macCatalyst)) && SWIFT_RUNTIME_OS_VERSIONING
59+
@_semantics("availability.osversion")
60+
@_effects(readnone)
61+
@available(macOS 10.15, iOS 13.0, *)
62+
public func _stdlib_isVariantOSVersionAtLeast(
63+
_ major: Builtin.Word,
64+
_ minor: Builtin.Word,
65+
_ patch: Builtin.Word
66+
) -> Builtin.Int1 {
67+
if Int(major) == 9999 {
68+
return true._value
69+
}
70+
let queryVersion = (Int(major), Int(minor), Int(patch))
71+
let major32 = Int32(truncatingIfNeeded:Int(queryVersion.0))
72+
let minor32 = Int32(truncatingIfNeeded:Int(queryVersion.1))
73+
let patch32 = Int32(truncatingIfNeeded:Int(queryVersion.2))
74+
75+
// Defer to a builtin that calls clang's version checking builtin from
76+
// compiler-rt.
77+
let result32 = Int32(Builtin.targetVariantOSVersionAtLeast(major32._value,
78+
minor32._value,
79+
patch32._value))
80+
return (result32 != (0 as Int32))._value
81+
}
82+
#endif
83+
84+
// Performs an availability check in zippered code to support back
85+
// deployment. This entry point takes in both a primary OS version
86+
// (i.e., a macOS version) and a variant OS version (i.e, an iOS version).
87+
//
88+
// In a normal macOS process it will return 1 if the running OS version is
89+
// greater than or equal to major.minor.patchVersion and 0 otherwise. For an
90+
// macCatalyst process it will return 1 if the running macCatalyst version is greater
91+
// than or equal to the passed-in variant version.
92+
//
93+
// Unlike _stdlib_isOSVersionAtLeast, this is not inlinable because we
94+
// don't want to inline the messy implementation details of the
95+
// compiler-rt support into apps and expose those as ABI surface.
96+
//
97+
// This is a magic entry point known to the compiler. It is called in
98+
// generated code for API availability checking.
99+
100+
#if (os(macOS) || os(iOS) && targetEnvironment(macCatalyst)) && SWIFT_RUNTIME_OS_VERSIONING
50101
@_semantics("availability.osversion")
51102
@_effects(readnone)
52103
@_unavailableInEmbedded
@@ -59,6 +110,33 @@ public func _stdlib_isOSVersionAtLeastOrVariantVersionAtLeast(
59110
_ variantPatch: Builtin.Word
60111
) -> Builtin.Int1 {
61112
return _stdlib_isOSVersionAtLeast(major, minor, patch)
113+
114+
// FIXME: Enable when __isPlatformOrVariantPlatformVersionAtLeast() support
115+
// is added to compiler-rt.
116+
#if false
117+
if Int(major) == 9999 {
118+
return true._value
119+
}
120+
let queryVersion = (Int(major), Int(minor), Int(patch))
121+
let queryVariantVersion =
122+
(Int(variantMajor), Int(variantMinor), Int(variantPatch))
123+
124+
let major32 = UInt32(truncatingIfNeeded:Int(queryVersion.0))
125+
let minor32 = UInt32(truncatingIfNeeded:Int(queryVersion.1))
126+
let patch32 = UInt32(truncatingIfNeeded:Int(queryVersion.2))
127+
128+
let variantMajor32 = UInt32(truncatingIfNeeded:Int(queryVariantVersion.0))
129+
let variantMinor32 = UInt32(truncatingIfNeeded:Int(queryVariantVersion.1))
130+
let variantPatch32 = UInt32(truncatingIfNeeded:Int(queryVariantVersion.2))
131+
132+
// Defer to a builtin that calls clang's version checking builtin from
133+
// compiler-rt.
134+
let result32 = Int32(Builtin.targetOSVersionOrVariantOSVersionAtLeast(
135+
major32._value, minor32._value, patch32._value,
136+
variantMajor32._value, variantMinor32._value, variantPatch32._value))
137+
138+
return (result32 != (0 as UInt32))._value
139+
#endif
62140
}
63141
#endif
64142

0 commit comments

Comments
 (0)