stdlib/SILGen: Emit SIL for Swift runtime availability queries.

When emitting SIL for `if #available(Swift ..., *)` queries, call the new
`_isSwiftRuntimeVersionAtLeast()` function in the stdlib to check the
condition. To support back deployment, the implementation of
`_isSwiftRuntimeVersionAtLeast()` is `@_alwaysEmitIntoClient` and performs its
comparison against the result of `_SwiftStdlibVersion.current`, which is
pre-existing ABI that the stdlib exposes for querying the Swift runtime
version.

Resolves rdar://162726037.
This commit is contained in:
Allan Shortlidge
2025-10-15 16:58:27 -07:00
parent bf4ec3e71a
commit 085f8ecca9
9 changed files with 224 additions and 10 deletions

View File

@@ -801,6 +801,9 @@ public:
// Swift._stdlib_isOSVersionAtLeastOrVariantVersionAtLeast.
FuncDecl *getIsOSVersionAtLeastOrVariantVersionAtLeast() const;
/// Retrieve the declaration of Swift._isSwiftRuntimeVersionAtLeast.
FuncDecl *getIsSwiftRuntimeVersionAtLeast() const;
/// Look for the declaration with the given name within the
/// passed in module.
void lookupInModule(ModuleDecl *M, StringRef name,

View File

@@ -47,9 +47,7 @@ class AvailabilityQuery final {
AvailabilityQuery(AvailabilityDomain domain, ResultKind kind,
const std::optional<AvailabilityRange> &primaryRange,
const std::optional<AvailabilityRange> &variantRange)
: domain(domain), primaryRange(primaryRange), variantRange(variantRange),
kind(kind), unavailable(false) {};
const std::optional<AvailabilityRange> &variantRange);
public:
/// Returns an `AvailabilityQuery` for a query that evaluates to true or

View File

@@ -425,6 +425,13 @@ struct ASTContext::Implementation {
/// -> Builtin.Int1
FuncDecl *IsOSVersionAtLeastOrVariantVersionAtLeastDecl = nullptr;
/// func _isSwiftRuntimeVersionAtLeast(
/// Builtin.Word,
/// Builtin.Word,
/// Builtin.word)
/// -> Builtin.Int1
FuncDecl *IsSwiftRuntimeVersionAtLeastDecl = nullptr;
/// The set of known protocols, lazily populated as needed.
ProtocolDecl *KnownProtocols[NumKnownProtocols] = { };
@@ -1934,7 +1941,7 @@ FuncDecl *ASTContext::getIsVariantOSVersionAtLeastDecl() const {
}
FuncDecl *ASTContext::getIsOSVersionAtLeastOrVariantVersionAtLeast() const {
if (getImpl().IsOSVersionAtLeastOrVariantVersionAtLeastDecl)
if (getImpl().IsOSVersionAtLeastOrVariantVersionAtLeastDecl)
return getImpl().IsOSVersionAtLeastOrVariantVersionAtLeastDecl;
auto decl = findLibraryIntrinsic(*this,
@@ -1946,6 +1953,18 @@ if (getImpl().IsOSVersionAtLeastOrVariantVersionAtLeastDecl)
return decl;
}
FuncDecl *ASTContext::getIsSwiftRuntimeVersionAtLeast() const {
if (getImpl().IsSwiftRuntimeVersionAtLeastDecl)
return getImpl().IsSwiftRuntimeVersionAtLeastDecl;
auto decl = findLibraryIntrinsic(*this, "_isSwiftRuntimeVersionAtLeast");
if (!decl)
return nullptr;
getImpl().IsSwiftRuntimeVersionAtLeastDecl = decl;
return decl;
}
static bool isHigherPrecedenceThan(PrecedenceGroupDecl *a,
PrecedenceGroupDecl *b) {
assert(a != b && "exact match should already have been filtered");

View File

@@ -17,6 +17,54 @@
using namespace swift;
AvailabilityQuery::AvailabilityQuery(
AvailabilityDomain domain, ResultKind kind,
const std::optional<AvailabilityRange> &primaryRange,
const std::optional<AvailabilityRange> &variantRange)
: domain(domain), primaryRange(primaryRange), variantRange(variantRange),
kind(kind), unavailable(false) {
// Check invariants.
switch (domain.getKind()) {
case AvailabilityDomain::Kind::SwiftLanguageMode:
case AvailabilityDomain::Kind::PackageDescription:
case AvailabilityDomain::Kind::Embedded:
// These domains don't support queries at all.
DEBUG_ASSERT(false);
break;
case AvailabilityDomain::Kind::Universal:
// The universal domain can only support constant queries.
DEBUG_ASSERT(kind != ResultKind::Dynamic);
break;
case AvailabilityDomain::Kind::SwiftRuntime:
// Dynamic Swift runtime queries take just a primary version argument.
if (kind == ResultKind::Dynamic) {
DEBUG_ASSERT(primaryRange);
DEBUG_ASSERT(!variantRange);
}
break;
case AvailabilityDomain::Kind::Platform:
// Dynamic platform version queries must have either a primary version
// argument or a variant version argument (or both).
if (kind == ResultKind::Dynamic) {
DEBUG_ASSERT(primaryRange || variantRange);
}
break;
case AvailabilityDomain::Kind::Custom:
// Custom availability domains do not support versioned queries at all yet.
DEBUG_ASSERT(!primaryRange);
DEBUG_ASSERT(!variantRange);
// A valid custom domain object is required.
auto customDomain = domain.getCustomDomain();
ASSERT(customDomain);
break;
}
}
static void unpackVersion(const llvm::VersionTuple &version,
llvm::SmallVectorImpl<unsigned> &arguments) {
arguments.push_back(version.getMajor());
@@ -133,16 +181,17 @@ FuncDecl *AvailabilityQuery::getDynamicQueryDeclAndArguments(
switch (domain.getKind()) {
case AvailabilityDomain::Kind::Universal:
case AvailabilityDomain::Kind::SwiftLanguageMode:
case AvailabilityDomain::Kind::SwiftRuntime:
case AvailabilityDomain::Kind::PackageDescription:
case AvailabilityDomain::Kind::Embedded:
// These domains don't support dynamic queries.
return nullptr;
case AvailabilityDomain::Kind::SwiftRuntime:
unpackVersion(getPrimaryArgument().value(), arguments);
return ctx.getIsSwiftRuntimeVersionAtLeast();
case AvailabilityDomain::Kind::Platform:
return getOSAvailabilityDeclAndArguments(*this, arguments, ctx);
case AvailabilityDomain::Kind::Custom:
auto customDomain = domain.getCustomDomain();
ASSERT(customDomain);
return customDomain->getPredicateFunc();
return domain.getCustomDomain()->getPredicateFunc();
}
}

View File

@@ -870,6 +870,8 @@ private:
variantRange);
case AvailabilityDomain::Kind::SwiftRuntime:
return AvailabilityQuery::dynamic(domain, primaryRange, std::nullopt);
case AvailabilityDomain::Kind::Platform:
// Platform and Swift runtime checks are always dynamic. The SIL optimizer
// is responsible eliminating these checks when it can prove that they can

View File

@@ -197,6 +197,22 @@ public func _stdlib_isOSVersionAtLeastOrVariantVersionAtLeast(
public typealias _SwiftStdlibVersion = SwiftShims._SwiftStdlibVersion
/// This is a magic entry point known to the compiler. It is called in
/// generated code for Swift runtime availability checking, e.g.
///
/// if #available(Swift 6.2, *) { }
///
@available(SwiftStdlib 5.7, *)
@_alwaysEmitIntoClient
internal func _isSwiftRuntimeVersionAtLeast(
_ major: Builtin.Word,
_ minor: Builtin.Word,
_ patch: Builtin.Word
) -> Builtin.Int1 {
let version = _SwiftStdlibVersion(major, minor, patch)
return (_SwiftStdlibVersion.current._value <= version._value)._value
}
/// Return true if the main executable was linked with an SDK version
/// corresponding to the given Swift Stdlib release, or later. Otherwise, return
/// false.
@@ -243,8 +259,36 @@ extension _SwiftStdlibVersion {
@_alwaysEmitIntoClient
public static var v6_3_0: Self { Self(_value: 0x060300) }
private static var _current: Self { .v6_3_0 }
#if hasFeature(Macros)
@available(SwiftStdlib 5.7, *)
public static var current: Self { .v6_3_0 }
public static var current: Self {
@_noLocks
@_effects(readnone)
get { ._current }
}
#else
@available(SwiftStdlib 5.7, *)
public static var current: Self {
@_effects(readnone)
get { ._current }
}
#endif
@_alwaysEmitIntoClient
internal init(
_ major: Builtin.Word,
_ minor: Builtin.Word,
_ patch: Builtin.Word
) {
let version = (Int(major), Int(minor), Int(patch))
var value: UInt32 = 0x0
value |= ((UInt32(truncatingIfNeeded: version.0) & 0xffff) << 16)
value |= ((UInt32(truncatingIfNeeded: version.1) & 0xff) << 8)
value |= ((UInt32(truncatingIfNeeded: version.2) & 0xff))
self = Self(_value: value)
}
}
@available(SwiftStdlib 5.7, *)

View File

@@ -0,0 +1,46 @@
// RUN: %target-swift-emit-ir %s -min-swift-runtime-version 5.0 -O -enable-experimental-feature SwiftRuntimeAvailability | %FileCheck %s
// REQUIRES: swift_feature_SwiftRuntimeAvailability
@_silgen_name("callMeMaybe")
public func callMeMaybe()
// Verify that optimized IR for "if #available(Swift X.Y, *)" is composed of a
// call to the stdlib ABI that returns the current runtime version and an
// integer comparison of that version and the predicate version.
// CHECK-LABEL: define {{.*}}swiftcc void @"$s26availability_swift_runtime15testIfAvailableyyF"()
// CHECK: [[CURRENT_VERS:%.*]] = tail call swiftcc i32 @"$sSo19_SwiftStdlibVersionasE7currentABvgZ"()
// CHECK: [[ICMP:%.*]] = icmp {{.*}}
// CHECK: br i1 [[ICMP]], label %[[TRUE_LABEL:.*]], label %[[FALSE_LABEL:.*]]
// CHECK: [[TRUE_LABEL]]:
// CHECK: call {{.*}} @callMeMaybe()
// CHECK: [[FALSE_LABEL]]:
// CHECK: ret void
public func testIfAvailable() {
if #available(Swift 6.2, *) {
callMeMaybe()
}
}
// In optimized IR multiple "if #available" checks for the same version should
// only generate a single call to the getter for _SwiftStdlibVersion.current.
// CHECK-LABEL: define {{.*}}swiftcc void @"$s26availability_swift_runtime23testIfAvailableMultipleyyF"()
// CHECK: call {{.*}} @"$sSo19_SwiftStdlibVersionasE7currentABvgZ"()
// CHECK-NOT: call {{.*}} @"$sSo19_SwiftStdlibVersionasE7currentABvgZ"()
// CHECK: icmp
// CHECK: call {{.*}} @callMeMaybe()
// CHECK: call {{.*}} @callMeMaybe()
// CHECK: call {{.*}} @callMeMaybe()
public func testIfAvailableMultiple() {
if #available(Swift 5.10, *) {
callMeMaybe()
}
if #available(Swift 5.10, *) {
callMeMaybe()
}
if #available(Swift 5.10, *) {
callMeMaybe()
}
}

View File

@@ -0,0 +1,26 @@
// RUN: %target-swift-emit-sil %s -min-swift-runtime-version 5.0 -verify -enable-experimental-feature SwiftRuntimeAvailability
// RUN: %target-swift-emit-silgen %s -min-swift-runtime-version 5.0 -enable-experimental-feature SwiftRuntimeAvailability | %FileCheck %s
// REQUIRES: swift_feature_SwiftRuntimeAvailability
// CHECK-LABEL: sil [ossa] @$s32availability_query_swift_runtime15testIfAvailableyyF : $@convention(thin) () -> () {
// CHECK: [[MAJOR:%.*]] = integer_literal $Builtin.Word, 6
// CHECK: [[MINOR:%.*]] = integer_literal $Builtin.Word, 2
// CHECK: [[PATCH:%.*]] = integer_literal $Builtin.Word, 0
// CHECK: [[FUNC:%.*]] = function_ref @$ss29_isSwiftRuntimeVersionAtLeastyBi1_Bw_BwBwtF : $@convention(thin) (Builtin.Word, Builtin.Word, Builtin.Word) -> Builtin.Int1
// CHECK: [[QUERY_RESULT:%.*]] = apply [[FUNC]]([[MAJOR]], [[MINOR]], [[PATCH]]) : $@convention(thin) (Builtin.Word, Builtin.Word, Builtin.Word) -> Builtin.Int1
public func testIfAvailable() {
if #available(Swift 6.2, *) { }
}
// CHECK-LABEL: sil [ossa] @$s32availability_query_swift_runtime17testIfUnavailableyyF : $@convention(thin) () -> () {
// CHECK: [[MAJOR:%.*]] = integer_literal $Builtin.Word, 5
// CHECK: [[MINOR:%.*]] = integer_literal $Builtin.Word, 10
// CHECK: [[PATCH:%.*]] = integer_literal $Builtin.Word, 1
// CHECK: [[FUNC:%.*]] = function_ref @$ss29_isSwiftRuntimeVersionAtLeastyBi1_Bw_BwBwtF : $@convention(thin) (Builtin.Word, Builtin.Word, Builtin.Word) -> Builtin.Int1
// CHECK: [[QUERY_RESULT:%.*]] = apply [[FUNC]]([[MAJOR]], [[MINOR]], [[PATCH]]) : $@convention(thin) (Builtin.Word, Builtin.Word, Builtin.Word) -> Builtin.Int1
// CHECK: [[MINUSONE:%.*]] = integer_literal $Builtin.Int1, -1
// CHECK: [[QUERY_INVERSION:%.*]] = builtin "xor_Int1"([[QUERY_RESULT]], [[MINUSONE]]) : $Builtin.Int1
public func testIfUnavailable() {
if #unavailable(Swift 5.10.1) { }
}

View File

@@ -0,0 +1,27 @@
// RUN: %target-swift-emit-sil %s -target %target-cpu-apple-macosx11 -target-variant %target-cpu-apple-ios14-macabi -min-swift-runtime-version 5.0 -verify -enable-experimental-feature SwiftRuntimeAvailability
// RUN: %target-swift-emit-silgen %s -target %target-cpu-apple-macosx11 -target-variant %target-cpu-apple-ios14-macabi -min-swift-runtime-version 5.0 -enable-experimental-feature SwiftRuntimeAvailability | %FileCheck %s
// REQUIRES: OS=macosx || OS=maccatalyst
// REQUIRES: swift_feature_SwiftRuntimeAvailability
// CHECK-LABEL: sil [ossa] @$s53availability_query_swift_runtime_maccatalyst_zippered15testIfAvailableyyF : $@convention(thin) () -> () {
// CHECK: [[MAJOR:%.*]] = integer_literal $Builtin.Word, 6
// CHECK: [[MINOR:%.*]] = integer_literal $Builtin.Word, 2
// CHECK: [[PATCH:%.*]] = integer_literal $Builtin.Word, 0
// CHECK: [[FUNC:%.*]] = function_ref @$ss29_isSwiftRuntimeVersionAtLeastyBi1_Bw_BwBwtF : $@convention(thin) (Builtin.Word, Builtin.Word, Builtin.Word) -> Builtin.Int1
// CHECK: [[QUERY_RESULT:%.*]] = apply [[FUNC]]([[MAJOR]], [[MINOR]], [[PATCH]]) : $@convention(thin) (Builtin.Word, Builtin.Word, Builtin.Word) -> Builtin.Int1
public func testIfAvailable() {
if #available(Swift 6.2, *) { }
}
// CHECK-LABEL: sil [ossa] @$s53availability_query_swift_runtime_maccatalyst_zippered17testIfUnavailableyyF : $@convention(thin) () -> () {
// CHECK: [[MAJOR:%.*]] = integer_literal $Builtin.Word, 5
// CHECK: [[MINOR:%.*]] = integer_literal $Builtin.Word, 10
// CHECK: [[PATCH:%.*]] = integer_literal $Builtin.Word, 1
// CHECK: [[FUNC:%.*]] = function_ref @$ss29_isSwiftRuntimeVersionAtLeastyBi1_Bw_BwBwtF : $@convention(thin) (Builtin.Word, Builtin.Word, Builtin.Word) -> Builtin.Int1
// CHECK: [[QUERY_RESULT:%.*]] = apply [[FUNC]]([[MAJOR]], [[MINOR]], [[PATCH]]) : $@convention(thin) (Builtin.Word, Builtin.Word, Builtin.Word) -> Builtin.Int1
// CHECK: [[MINUSONE:%.*]] = integer_literal $Builtin.Int1, -1
// CHECK: [[QUERY_INVERSION:%.*]] = builtin "xor_Int1"([[QUERY_RESULT]], [[MINUSONE]]) : $Builtin.Int1
public func testIfUnavailable() {
if #unavailable(Swift 5.10.1) { }
}