Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,9 @@ class ASTContext final {
// 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,
Expand Down
4 changes: 1 addition & 3 deletions include/swift/AST/AvailabilityQuery.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 20 additions & 1 deletion lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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] = { };

Expand Down Expand Up @@ -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,
Expand All @@ -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");
Expand Down
59 changes: 54 additions & 5 deletions lib/AST/AvailabilityQuery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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();
}
}
2 changes: 2 additions & 0 deletions lib/AST/AvailabilityScopeBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,8 @@ class AvailabilityScopeBuilder : private ASTWalker {
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
Expand Down
46 changes: 45 additions & 1 deletion stdlib/public/core/Availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
@_noLocks
@_effects(readnone)
get { ._current }
}
#else
@available(SwiftStdlib 5.7, *)
public static var current: Self { .v6_3_0 }
public static var current: Self {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This duplication is a workaround for the fact that the parser doesn't seem to support wrapping @_noLocks in conditional compilation guards when the attribute is attached to an accessor.

@_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, *)
Expand Down
46 changes: 46 additions & 0 deletions test/IRGen/availability_swift_runtime.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}
26 changes: 26 additions & 0 deletions test/SILGen/availability_query_swift_runtime.swift
Original file line number Diff line number Diff line change
@@ -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) { }
}
Original file line number Diff line number Diff line change
@@ -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) { }
}