Skip to content

Commit cdd627d

Browse files
committed
AST: Enable -unavailable-decl-optimization on visionOS.
Using availability domains, reimplement the algorithm that determines whether a declaration is unavailable at runtime. The new algorithm takes ABI compatible platforms into account, ensuring that declarations that are available on iOS do not get treated as unreachable at runtime when compiling for visionOS. Resolves rdar://116742214.
1 parent ac7654e commit cdd627d

File tree

5 files changed

+107
-29
lines changed

5 files changed

+107
-29
lines changed

include/swift/AST/AvailabilityDomain.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ class AvailabilityDomain final {
234234
return !(*this == other);
235235
}
236236

237+
friend bool operator<(const AvailabilityDomain &lhs,
238+
const AvailabilityDomain &rhs) {
239+
return lhs.storage.getOpaqueValue() < rhs.storage.getOpaqueValue();
240+
}
241+
237242
void Profile(llvm::FoldingSetNodeID &ID) const {
238243
ID.AddPointer(getOpaqueValue());
239244
}

lib/AST/Availability.cpp

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
#include "swift/AST/ASTContext.h"
1818
#include "swift/AST/Attr.h"
19+
#include "swift/AST/AvailabilityConstraint.h"
20+
#include "swift/AST/AvailabilityContext.h"
1921
#include "swift/AST/AvailabilityDomain.h"
2022
#include "swift/AST/AvailabilityInference.h"
2123
#include "swift/AST/AvailabilityRange.h"
@@ -629,27 +631,78 @@ Decl::getUnavailableAttr(bool ignoreAppExtensions) const {
629631
return std::nullopt;
630632
}
631633

632-
static bool isDeclCompletelyUnavailable(const Decl *decl) {
634+
/// Returns true if \p decl is proven to be unavailable for all platforms that
635+
/// external modules interacting with this module could target. A declaration
636+
/// that is not proven to be unavailable in this way could be reachable at
637+
/// runtime, even if it is unavailable to all code in this module.
638+
static bool isUnavailableForAllABICompatiblePlatforms(const Decl *decl) {
633639
// Don't trust unavailability on declarations from clang modules.
634640
if (isa<ClangModuleUnit>(decl->getDeclContext()->getModuleScopeContext()))
635641
return false;
636642

637-
auto unavailableAttr = decl->getUnavailableAttr(/*ignoreAppExtensions=*/true);
638-
if (!unavailableAttr)
643+
auto &ctx = decl->getASTContext();
644+
llvm::SmallVector<AvailabilityDomain, 2> compatibilityDomains;
645+
if (auto targetDomain = AvailabilityDomain::forTargetPlatform(ctx))
646+
compatibilityDomains.push_back(targetDomain->getABICompatibilityDomain());
647+
648+
llvm::SmallSet<AvailabilityDomain, 8> unavailablePlatformDomains;
649+
llvm::SmallSet<AvailabilityDomain, 8> availablePlatformDomains;
650+
651+
/// Returns whether the domain is either active for the compilation or is
652+
/// contained by one of the ABI compatibility domains.
653+
auto isRelevantDomain = [&](AvailabilityDomain domain) {
654+
if (domain.isActive(ctx))
655+
return true;
656+
657+
for (auto compatibilityDomain : compatibilityDomains) {
658+
if (compatibilityDomain.contains(domain))
659+
return true;
660+
}
661+
639662
return false;
663+
};
664+
665+
// Build up the collection of relevant available and unavailable platform
666+
// domains by looking at all the @available attributes. Along the way, we
667+
// may find an attribute that makes the declaration universally unavailable
668+
// in which case platform availability is irrelevant.
669+
for (auto attr : decl->getSemanticAvailableAttrs(/*includeInactive=*/true)) {
670+
auto domain = attr.getDomain();
671+
if (!isRelevantDomain(domain))
672+
continue;
673+
674+
if (!domain.isPlatform()) {
675+
// This attribute is not platform-specific so if it makes the declaration
676+
// unconditionally unavailable then we're done.
677+
if (attr.isUnconditionallyUnavailable())
678+
return true;
679+
continue;
680+
}
640681

641-
// getUnavailableAttr() can return an @available attribute that is
642-
// obsoleted for certain deployment targets or language modes. These decls
643-
// can still be reached by code in other modules that is compiled with
644-
// a different deployment target or language mode.
645-
if (!unavailableAttr->isUnconditionallyUnavailable())
682+
// Record the availability indicated by this platform-specific attribute.
683+
// If it indicates unconditional unavailability, that overrides any previous
684+
// attributes for the same domain.
685+
if (attr.isUnconditionallyUnavailable()) {
686+
availablePlatformDomains.erase(domain);
687+
unavailablePlatformDomains.insert(domain);
688+
} else if (!unavailablePlatformDomains.contains(domain)) {
689+
availablePlatformDomains.insert(domain);
690+
}
691+
}
692+
693+
// Check if the declaration could be available to clients compiling for a
694+
// a derived platform.
695+
if (availablePlatformDomains.size() > 0)
646696
return false;
647697

648-
// Universally unavailable declarations are always completely unavailable.
649-
if (unavailableAttr->getPlatform() == PlatformKind::none)
650-
return true;
698+
// Verify whether the declaration has been marked explicitly unavailable on
699+
// each of the ABI compatibility domains.
700+
for (auto compatibilityDomain : compatibilityDomains) {
701+
if (!unavailablePlatformDomains.contains(compatibilityDomain))
702+
return false;
703+
}
651704

652-
// FIXME: Support zippered frameworks (rdar://125371621)
705+
// FIXME: [availability] Support zippered frameworks (rdar://125371621)
653706
// If we have a target variant (e.g. we're building a zippered macOS
654707
// framework) then the decl is only unreachable if it is unavailable for both
655708
// the primary target and the target variant.
@@ -670,7 +723,7 @@ SemanticDeclAvailabilityRequest::evaluate(Evaluator &evaluator,
670723
}
671724

672725
if (inherited == SemanticDeclAvailability::CompletelyUnavailable ||
673-
isDeclCompletelyUnavailable(decl))
726+
isUnavailableForAllABICompatiblePlatforms(decl))
674727
return SemanticDeclAvailability::CompletelyUnavailable;
675728

676729
if (inherited == SemanticDeclAvailability::ConditionallyUnavailable ||
@@ -699,14 +752,6 @@ getEffectiveUnavailableDeclOptimization(ASTContext &ctx) {
699752
if (ctx.LangOpts.UnavailableDeclOptimizationMode.has_value())
700753
return *ctx.LangOpts.UnavailableDeclOptimizationMode;
701754

702-
// FIXME: Allow unavailable decl optimization on visionOS.
703-
// visionOS must be ABI compatible with iOS. Enabling unavailable declaration
704-
// optimizations naively would break compatibility since declarations marked
705-
// unavailable on visionOS would be optimized regardless of whether they are
706-
// available on iOS. rdar://116742214
707-
if (ctx.LangOpts.Target.isXROS())
708-
return UnavailableDeclOptimization::None;
709-
710755
return UnavailableDeclOptimization::None;
711756
}
712757

test/Concurrency/isolation_macro_availability.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
// REQUIRES: concurrency
44
// REQUIRES: swift_swift_parser
5-
// REQUIRES: VENDOR=apple
5+
// REQUIRES: OS=macosx || OS=ios || OS=tvos || OS=watchos
66

77
// rdar://126118470
88
// UNSUPPORTED: CPU=arm64e

test/SILGen/unavailable_decl_optimization_stub_macos.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ public func unavailableOnMacOSExtensionFunc() {}
4444
@available(macOSApplicationExtension, unavailable) // FIXME: Seems like this should be diagnosed as redundant
4545
public func unavailableOnMacOSAndMacOSExtensionFunc() {}
4646

47+
// CHECK-LABEL: sil{{.*}}@$s4Test33availableOnMacOSExtensionOnlyFuncyyF
48+
// CHECK-NOT: _diagnoseUnavailableCodeReached
49+
// CHECK: } // end sil function '$s4Test33availableOnMacOSExtensionOnlyFuncyyF'
50+
@available(macOS, unavailable)
51+
@available(macOSApplicationExtension, introduced: 10.9)
52+
public func availableOnMacOSExtensionOnlyFunc() {}
53+
4754
// CHECK-LABEL: sil{{.*}}@$s4Test20unavailableOniOSFuncyyF
4855
// CHECK-NOT: _diagnoseUnavailableCodeReached
4956
// CHECK: } // end sil function '$s4Test20unavailableOniOSFuncyyF'

test/SILGen/unavailable_decl_optimization_visionos.swift renamed to test/SILGen/unavailable_decl_optimization_stub_visionos.swift

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %target-swift-emit-silgen -module-name Test -parse-as-library %s -verify -target arm64-apple-xros1.0 | %FileCheck %s --check-prefixes=CHECK
2-
// RUN: %target-swift-emit-silgen -module-name Test -parse-as-library %s -verify -unavailable-decl-optimization=none -target arm64-apple-xros1.0 | %FileCheck %s --check-prefixes=CHECK
1+
// RUN: %target-swift-emit-silgen -module-name Test -parse-as-library %s -verify -unavailable-decl-optimization=stub -target arm64-apple-xros1.0 | %FileCheck %s --check-prefixes=CHECK
2+
// RUN: %target-swift-emit-silgen -module-name Test -parse-as-library %s -verify -unavailable-decl-optimization=stub -target arm64-apple-xros1.0 -application-extension | %FileCheck %s --check-prefixes=CHECK
33

44
// REQUIRES: OS=xros
55

@@ -14,7 +14,8 @@ public func visionOSUnavailable() -> S {
1414
}
1515

1616
// CHECK-LABEL: sil{{.*}}@$s4Test14iOSUnavailableAA1SVyF
17-
// CHECK-NOT: ss31_diagnoseUnavailableCodeReacheds5NeverOyF
17+
// CHECK: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyF : $@convention(thin) () -> Never
18+
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
1819
// CHECK: } // end sil function '$s4Test14iOSUnavailableAA1SVyF'
1920
@available(iOS, unavailable)
2021
public func iOSUnavailable() -> S {
@@ -39,12 +40,32 @@ public func iOSUnavailableVisionOSAvailable() -> S {
3940
}
4041

4142
// CHECK-LABEL: sil{{.*}}@$s4Test25iOSAndVisionOSUnavailableAA1SVyF
42-
// CHECK-NOT: ss31_diagnoseUnavailableCodeReacheds5NeverOyF
43+
// CHECK: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyF : $@convention(thin) () -> Never
44+
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
4345
// CHECK: } // end sil function '$s4Test25iOSAndVisionOSUnavailableAA1SVyF'
4446
@available(iOS, unavailable)
4547
@available(visionOS, unavailable)
4648
public func iOSAndVisionOSUnavailable() -> S {
47-
// FIXME: This function should be optimized (rdar://116742214)
49+
return S()
50+
}
51+
52+
// CHECK-LABEL: sil{{.*}}@$s4Test20iOSAppExtensionsOnlyAA1SVyF
53+
// CHECK-NOT: ss31_diagnoseUnavailableCodeReacheds5NeverOyF
54+
// CHECK: } // end sil function '$s4Test20iOSAppExtensionsOnlyAA1SVyF'
55+
@available(iOS, unavailable)
56+
@available(visionOS, unavailable)
57+
@available(iOSApplicationExtension, introduced: 1.0)
58+
public func iOSAppExtensionsOnly() -> S {
59+
return S()
60+
}
61+
62+
// CHECK-LABEL: sil{{.*}}@$s4Test25visionOSAppExtensionsOnlyAA1SVyF
63+
// CHECK-NOT: ss31_diagnoseUnavailableCodeReacheds5NeverOyF
64+
// CHECK: } // end sil function '$s4Test25visionOSAppExtensionsOnlyAA1SVyF'
65+
@available(iOS, unavailable)
66+
@available(visionOS, unavailable)
67+
@available(visionOSApplicationExtension, introduced: 1.0)
68+
public func visionOSAppExtensionsOnly() -> S {
4869
return S()
4970
}
5071

@@ -58,11 +79,11 @@ public struct UnavailableOnVisionOS {
5879
}
5980

6081
// CHECK-LABEL: sil{{.*}}@$s4Test21UnavailableOnVisionOSV022iOSUnavailableInheritsdF0AA1SVyF
61-
// CHECK-NOT: ss31_diagnoseUnavailableCodeReacheds5NeverOyF
82+
// CHECK: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyF : $@convention(thin) () -> Never
83+
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
6284
// CHECK: } // end sil function '$s4Test21UnavailableOnVisionOSV022iOSUnavailableInheritsdF0AA1SVyF'
6385
@available(iOS, unavailable)
6486
public func iOSUnavailableInheritsVisionOSUnavailable() -> S {
65-
// FIXME: This function should be optimized (rdar://116742214)
6687
return S()
6788
}
6889
}

0 commit comments

Comments
 (0)