Skip to content

Commit b9e1cbd

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 fa44b9e commit b9e1cbd

File tree

6 files changed

+120
-35
lines changed

6 files changed

+120
-35
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: 74 additions & 23 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,84 @@ Decl::getUnavailableAttr(bool ignoreAppExtensions) const {
629631
return std::nullopt;
630632
}
631633

632-
static bool isDeclCompletelyUnavailable(const Decl *decl) {
633-
// Don't trust unavailability on declarations from clang modules.
634+
static llvm::SmallVector<AvailabilityDomain, 2>
635+
availabilityDomainsForABICompatibility(const ASTContext &ctx) {
636+
llvm::SmallVector<AvailabilityDomain, 2> domains;
637+
638+
// Regardless of target platform, binaries built for Embedded do not require
639+
// compatibility.
640+
if (ctx.LangOpts.hasFeature(Feature::Embedded))
641+
return domains;
642+
643+
if (auto targetDomain = AvailabilityDomain::forTargetPlatform(ctx))
644+
domains.push_back(targetDomain->getABICompatibilityDomain());
645+
646+
return domains;
647+
}
648+
649+
/// Returns true if \p decl is proven to be unavailable for all platforms that
650+
/// external modules interacting with this module could target. A declaration
651+
/// that is not proven to be unavailable in this way could be reachable at
652+
/// runtime, even if it is unavailable to all code in this module.
653+
static bool isUnavailableForAllABICompatiblePlatforms(const Decl *decl) {
654+
// Don't trust unavailability on declarations from Clang modules.
634655
if (isa<ClangModuleUnit>(decl->getDeclContext()->getModuleScopeContext()))
635656
return false;
636657

637-
auto unavailableAttr = decl->getUnavailableAttr(/*ignoreAppExtensions=*/true);
638-
if (!unavailableAttr)
639-
return false;
658+
auto &ctx = decl->getASTContext();
659+
llvm::SmallVector<AvailabilityDomain, 2> compatibilityDomains =
660+
availabilityDomainsForABICompatibility(ctx);
661+
662+
llvm::SmallSet<AvailabilityDomain, 8> unavailableDescendantDomains;
663+
llvm::SmallSet<AvailabilityDomain, 8> availableDescendantDomains;
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+
bool isCompabilityDomainDescendant =
672+
llvm::find_if(compatibilityDomains,
673+
[&domain](AvailabilityDomain compatibilityDomain) {
674+
return compatibilityDomain.contains(domain);
675+
}) != compatibilityDomains.end();
676+
677+
if (isCompabilityDomainDescendant) {
678+
// Record the whether the descendant domain is marked available
679+
// or unavailable. Unavailability overrides availability.
680+
if (attr.isUnconditionallyUnavailable()) {
681+
availableDescendantDomains.erase(domain);
682+
unavailableDescendantDomains.insert(domain);
683+
} else if (!unavailableDescendantDomains.contains(domain)) {
684+
availableDescendantDomains.insert(domain);
685+
}
686+
} else if (attr.isActive(ctx)) {
687+
// The declaration is always unavailable if an active attribute from a
688+
// domain outside the compatibility hierarchy indicates unavailability.
689+
if (attr.isUnconditionallyUnavailable())
690+
return true;
691+
}
692+
}
640693

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())
694+
// If there aren't any compatibility domains to check and we didn't find any
695+
// other active attributes that make the declaration unavailable, then it must
696+
// be available.
697+
if (compatibilityDomains.empty())
646698
return false;
647699

648-
// Universally unavailable declarations are always completely unavailable.
649-
if (unavailableAttr->getPlatform() == PlatformKind::none)
650-
return true;
700+
// Verify that the declaration has been marked unavailable in every
701+
// compatibility domain.
702+
for (auto compatibilityDomain : compatibilityDomains) {
703+
if (!unavailableDescendantDomains.contains(compatibilityDomain))
704+
return false;
705+
}
706+
707+
// Verify that there aren't any explicitly available descendant domains.
708+
if (availableDescendantDomains.size() > 0)
709+
return false;
651710

652-
// FIXME: Support zippered frameworks (rdar://125371621)
711+
// FIXME: [availability] Support zippered frameworks (rdar://125371621)
653712
// If we have a target variant (e.g. we're building a zippered macOS
654713
// framework) then the decl is only unreachable if it is unavailable for both
655714
// the primary target and the target variant.
@@ -670,7 +729,7 @@ SemanticDeclAvailabilityRequest::evaluate(Evaluator &evaluator,
670729
}
671730

672731
if (inherited == SemanticDeclAvailability::CompletelyUnavailable ||
673-
isDeclCompletelyUnavailable(decl))
732+
isUnavailableForAllABICompatiblePlatforms(decl))
674733
return SemanticDeclAvailability::CompletelyUnavailable;
675734

676735
if (inherited == SemanticDeclAvailability::ConditionallyUnavailable ||
@@ -699,14 +758,6 @@ getEffectiveUnavailableDeclOptimization(ASTContext &ctx) {
699758
if (ctx.LangOpts.UnavailableDeclOptimizationMode.has_value())
700759
return *ctx.LangOpts.UnavailableDeclOptimizationMode;
701760

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-
710761
return UnavailableDeclOptimization::None;
711762
}
712763

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
}

test/embedded/availability-code-removal.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
// - (2) unavailable function bodies is removed in embedded Swift,
88
// - (3) the test() function is not reported by the existential checker.
99

10-
// RUN: %target-swift-frontend -emit-ir %s -parse-stdlib -wmo | %FileCheck %s --check-prefix CHECK-A
11-
// RUN: %target-swift-frontend -emit-ir %s -parse-stdlib -enable-experimental-feature Embedded -wmo | %FileCheck %s --check-prefix CHECK-B
10+
// RUN: %target-swift-frontend -emit-ir %s -parse-stdlib -wmo | %FileCheck %s --check-prefix CHECK-NONEMBEDDED
11+
// RUN: %target-swift-frontend -emit-ir %s -parse-stdlib -enable-experimental-feature Embedded -wmo | %FileCheck %s --check-prefix CHECK-EMBEDDED
12+
// RUN: %target-swift-frontend -emit-ir %s -parse-stdlib -enable-experimental-feature Embedded -target arm64e-apple-none -wmo | %FileCheck %s --check-prefix CHECK-EMBEDDED
1213

1314
// REQUIRES: swift_in_compiler
1415
// REQUIRES: swift_feature_Embedded
@@ -21,5 +22,5 @@ public func test() -> any Player {
2122
Concrete() // no error because we're in unavailable-in-embedded context
2223
}
2324

24-
// CHECK-A: $s4main4testAA6Player_pyF
25-
// CHECK-B-NOT: $e4main4testAA6Player_pyF
25+
// CHECK-NONEMBEDDED: $s4main4testAA6Player_pyF
26+
// CHECK-EMBEDDED-NOT: $e4main4testAA6Player_pyF

0 commit comments

Comments
 (0)