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.
This commit is contained in:
Allan Shortlidge
2025-02-08 22:22:28 -08:00
parent fa44b9e0b4
commit b9e1cbd3ac
6 changed files with 120 additions and 35 deletions

View File

@@ -234,6 +234,11 @@ public:
return !(*this == other);
}
friend bool operator<(const AvailabilityDomain &lhs,
const AvailabilityDomain &rhs) {
return lhs.storage.getOpaqueValue() < rhs.storage.getOpaqueValue();
}
void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddPointer(getOpaqueValue());
}

View File

@@ -16,6 +16,8 @@
#include "swift/AST/ASTContext.h"
#include "swift/AST/Attr.h"
#include "swift/AST/AvailabilityConstraint.h"
#include "swift/AST/AvailabilityContext.h"
#include "swift/AST/AvailabilityDomain.h"
#include "swift/AST/AvailabilityInference.h"
#include "swift/AST/AvailabilityRange.h"
@@ -629,27 +631,84 @@ Decl::getUnavailableAttr(bool ignoreAppExtensions) const {
return std::nullopt;
}
static bool isDeclCompletelyUnavailable(const Decl *decl) {
// Don't trust unavailability on declarations from clang modules.
static llvm::SmallVector<AvailabilityDomain, 2>
availabilityDomainsForABICompatibility(const ASTContext &ctx) {
llvm::SmallVector<AvailabilityDomain, 2> domains;
// Regardless of target platform, binaries built for Embedded do not require
// compatibility.
if (ctx.LangOpts.hasFeature(Feature::Embedded))
return domains;
if (auto targetDomain = AvailabilityDomain::forTargetPlatform(ctx))
domains.push_back(targetDomain->getABICompatibilityDomain());
return domains;
}
/// Returns true if \p decl is proven to be unavailable for all platforms that
/// external modules interacting with this module could target. A declaration
/// that is not proven to be unavailable in this way could be reachable at
/// runtime, even if it is unavailable to all code in this module.
static bool isUnavailableForAllABICompatiblePlatforms(const Decl *decl) {
// Don't trust unavailability on declarations from Clang modules.
if (isa<ClangModuleUnit>(decl->getDeclContext()->getModuleScopeContext()))
return false;
auto unavailableAttr = decl->getUnavailableAttr(/*ignoreAppExtensions=*/true);
if (!unavailableAttr)
auto &ctx = decl->getASTContext();
llvm::SmallVector<AvailabilityDomain, 2> compatibilityDomains =
availabilityDomainsForABICompatibility(ctx);
llvm::SmallSet<AvailabilityDomain, 8> unavailableDescendantDomains;
llvm::SmallSet<AvailabilityDomain, 8> availableDescendantDomains;
// Build up the collection of relevant available and unavailable platform
// domains by looking at all the @available attributes. Along the way, we
// may find an attribute that makes the declaration universally unavailable
// in which case platform availability is irrelevant.
for (auto attr : decl->getSemanticAvailableAttrs(/*includeInactive=*/true)) {
auto domain = attr.getDomain();
bool isCompabilityDomainDescendant =
llvm::find_if(compatibilityDomains,
[&domain](AvailabilityDomain compatibilityDomain) {
return compatibilityDomain.contains(domain);
}) != compatibilityDomains.end();
if (isCompabilityDomainDescendant) {
// Record the whether the descendant domain is marked available
// or unavailable. Unavailability overrides availability.
if (attr.isUnconditionallyUnavailable()) {
availableDescendantDomains.erase(domain);
unavailableDescendantDomains.insert(domain);
} else if (!unavailableDescendantDomains.contains(domain)) {
availableDescendantDomains.insert(domain);
}
} else if (attr.isActive(ctx)) {
// The declaration is always unavailable if an active attribute from a
// domain outside the compatibility hierarchy indicates unavailability.
if (attr.isUnconditionallyUnavailable())
return true;
}
}
// If there aren't any compatibility domains to check and we didn't find any
// other active attributes that make the declaration unavailable, then it must
// be available.
if (compatibilityDomains.empty())
return false;
// getUnavailableAttr() can return an @available attribute that is
// obsoleted for certain deployment targets or language modes. These decls
// can still be reached by code in other modules that is compiled with
// a different deployment target or language mode.
if (!unavailableAttr->isUnconditionallyUnavailable())
// Verify that the declaration has been marked unavailable in every
// compatibility domain.
for (auto compatibilityDomain : compatibilityDomains) {
if (!unavailableDescendantDomains.contains(compatibilityDomain))
return false;
}
// Verify that there aren't any explicitly available descendant domains.
if (availableDescendantDomains.size() > 0)
return false;
// Universally unavailable declarations are always completely unavailable.
if (unavailableAttr->getPlatform() == PlatformKind::none)
return true;
// FIXME: Support zippered frameworks (rdar://125371621)
// FIXME: [availability] Support zippered frameworks (rdar://125371621)
// If we have a target variant (e.g. we're building a zippered macOS
// framework) then the decl is only unreachable if it is unavailable for both
// the primary target and the target variant.
@@ -670,7 +729,7 @@ SemanticDeclAvailabilityRequest::evaluate(Evaluator &evaluator,
}
if (inherited == SemanticDeclAvailability::CompletelyUnavailable ||
isDeclCompletelyUnavailable(decl))
isUnavailableForAllABICompatiblePlatforms(decl))
return SemanticDeclAvailability::CompletelyUnavailable;
if (inherited == SemanticDeclAvailability::ConditionallyUnavailable ||
@@ -699,14 +758,6 @@ getEffectiveUnavailableDeclOptimization(ASTContext &ctx) {
if (ctx.LangOpts.UnavailableDeclOptimizationMode.has_value())
return *ctx.LangOpts.UnavailableDeclOptimizationMode;
// FIXME: Allow unavailable decl optimization on visionOS.
// visionOS must be ABI compatible with iOS. Enabling unavailable declaration
// optimizations naively would break compatibility since declarations marked
// unavailable on visionOS would be optimized regardless of whether they are
// available on iOS. rdar://116742214
if (ctx.LangOpts.Target.isXROS())
return UnavailableDeclOptimization::None;
return UnavailableDeclOptimization::None;
}

View File

@@ -2,7 +2,7 @@
// REQUIRES: concurrency
// REQUIRES: swift_swift_parser
// REQUIRES: VENDOR=apple
// REQUIRES: OS=macosx || OS=ios || OS=tvos || OS=watchos
// rdar://126118470
// UNSUPPORTED: CPU=arm64e

View File

@@ -44,6 +44,13 @@ public func unavailableOnMacOSExtensionFunc() {}
@available(macOSApplicationExtension, unavailable) // FIXME: Seems like this should be diagnosed as redundant
public func unavailableOnMacOSAndMacOSExtensionFunc() {}
// CHECK-LABEL: sil{{.*}}@$s4Test33availableOnMacOSExtensionOnlyFuncyyF
// CHECK-NOT: _diagnoseUnavailableCodeReached
// CHECK: } // end sil function '$s4Test33availableOnMacOSExtensionOnlyFuncyyF'
@available(macOS, unavailable)
@available(macOSApplicationExtension, introduced: 10.9)
public func availableOnMacOSExtensionOnlyFunc() {}
// CHECK-LABEL: sil{{.*}}@$s4Test20unavailableOniOSFuncyyF
// CHECK-NOT: _diagnoseUnavailableCodeReached
// CHECK: } // end sil function '$s4Test20unavailableOniOSFuncyyF'

View File

@@ -1,5 +1,5 @@
// RUN: %target-swift-emit-silgen -module-name Test -parse-as-library %s -verify -target arm64-apple-xros1.0 | %FileCheck %s --check-prefixes=CHECK
// 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
// 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
// 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
// REQUIRES: OS=xros
@@ -14,7 +14,8 @@ public func visionOSUnavailable() -> S {
}
// CHECK-LABEL: sil{{.*}}@$s4Test14iOSUnavailableAA1SVyF
// CHECK-NOT: ss31_diagnoseUnavailableCodeReacheds5NeverOyF
// CHECK: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyF : $@convention(thin) () -> Never
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
// CHECK: } // end sil function '$s4Test14iOSUnavailableAA1SVyF'
@available(iOS, unavailable)
public func iOSUnavailable() -> S {
@@ -39,12 +40,32 @@ public func iOSUnavailableVisionOSAvailable() -> S {
}
// CHECK-LABEL: sil{{.*}}@$s4Test25iOSAndVisionOSUnavailableAA1SVyF
// CHECK-NOT: ss31_diagnoseUnavailableCodeReacheds5NeverOyF
// CHECK: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyF : $@convention(thin) () -> Never
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
// CHECK: } // end sil function '$s4Test25iOSAndVisionOSUnavailableAA1SVyF'
@available(iOS, unavailable)
@available(visionOS, unavailable)
public func iOSAndVisionOSUnavailable() -> S {
// FIXME: This function should be optimized (rdar://116742214)
return S()
}
// CHECK-LABEL: sil{{.*}}@$s4Test20iOSAppExtensionsOnlyAA1SVyF
// CHECK-NOT: ss31_diagnoseUnavailableCodeReacheds5NeverOyF
// CHECK: } // end sil function '$s4Test20iOSAppExtensionsOnlyAA1SVyF'
@available(iOS, unavailable)
@available(visionOS, unavailable)
@available(iOSApplicationExtension, introduced: 1.0)
public func iOSAppExtensionsOnly() -> S {
return S()
}
// CHECK-LABEL: sil{{.*}}@$s4Test25visionOSAppExtensionsOnlyAA1SVyF
// CHECK-NOT: ss31_diagnoseUnavailableCodeReacheds5NeverOyF
// CHECK: } // end sil function '$s4Test25visionOSAppExtensionsOnlyAA1SVyF'
@available(iOS, unavailable)
@available(visionOS, unavailable)
@available(visionOSApplicationExtension, introduced: 1.0)
public func visionOSAppExtensionsOnly() -> S {
return S()
}
@@ -58,11 +79,11 @@ public struct UnavailableOnVisionOS {
}
// CHECK-LABEL: sil{{.*}}@$s4Test21UnavailableOnVisionOSV022iOSUnavailableInheritsdF0AA1SVyF
// CHECK-NOT: ss31_diagnoseUnavailableCodeReacheds5NeverOyF
// CHECK: [[FNREF:%.*]] = function_ref @$ss31_diagnoseUnavailableCodeReacheds5NeverOyF : $@convention(thin) () -> Never
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
// CHECK: } // end sil function '$s4Test21UnavailableOnVisionOSV022iOSUnavailableInheritsdF0AA1SVyF'
@available(iOS, unavailable)
public func iOSUnavailableInheritsVisionOSUnavailable() -> S {
// FIXME: This function should be optimized (rdar://116742214)
return S()
}
}

View File

@@ -7,8 +7,9 @@
// - (2) unavailable function bodies is removed in embedded Swift,
// - (3) the test() function is not reported by the existential checker.
// RUN: %target-swift-frontend -emit-ir %s -parse-stdlib -wmo | %FileCheck %s --check-prefix CHECK-A
// RUN: %target-swift-frontend -emit-ir %s -parse-stdlib -enable-experimental-feature Embedded -wmo | %FileCheck %s --check-prefix CHECK-B
// RUN: %target-swift-frontend -emit-ir %s -parse-stdlib -wmo | %FileCheck %s --check-prefix CHECK-NONEMBEDDED
// RUN: %target-swift-frontend -emit-ir %s -parse-stdlib -enable-experimental-feature Embedded -wmo | %FileCheck %s --check-prefix CHECK-EMBEDDED
// RUN: %target-swift-frontend -emit-ir %s -parse-stdlib -enable-experimental-feature Embedded -target arm64e-apple-none -wmo | %FileCheck %s --check-prefix CHECK-EMBEDDED
// REQUIRES: swift_in_compiler
// REQUIRES: swift_feature_Embedded
@@ -21,5 +22,5 @@ public func test() -> any Player {
Concrete() // no error because we're in unavailable-in-embedded context
}
// CHECK-A: $s4main4testAA6Player_pyF
// CHECK-B-NOT: $e4main4testAA6Player_pyF
// CHECK-NONEMBEDDED: $s4main4testAA6Player_pyF
// CHECK-EMBEDDED-NOT: $e4main4testAA6Player_pyF