Skip to content

[5.9] Introduce -unavailable-decl-optimization #64810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
20 changes: 20 additions & 0 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,22 @@ namespace swift {
TaskToThread,
};

/// Describes the code size optimization behavior for code associated with
/// declarations that are marked unavailable.
enum class UnavailableDeclOptimization : uint8_t {
/// No optimization. Unavailable declarations will contribute to the
/// resulting binary by default in this mode.
None,

/// Avoid generating any code for unavailable declarations.
///
/// NOTE: This optimization can be ABI breaking for a library evolution
/// enabled module because existing client binaries built with a
/// pre-Swift 5.9 compiler may depend on linkable symbols associated with
/// unavailable declarations.
Complete,
};

/// A collection of options that affect the language dialect and
/// provide compiler debugging facilities.
class LangOptions final {
Expand Down Expand Up @@ -174,6 +190,10 @@ namespace swift {
/// Only check the availability of the API, ignore function bodies.
bool CheckAPIAvailabilityOnly = false;

/// Optimization mode for unavailable declarations.
UnavailableDeclOptimization UnavailableDeclOptimizationMode =
UnavailableDeclOptimization::None;

/// Causes the compiler to use weak linkage for symbols belonging to
/// declarations introduced at the deployment target.
bool WeakLinkAtTarget = false;
Expand Down
7 changes: 7 additions & 0 deletions include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,13 @@ def check_api_availability_only : Flag<["-"], "check-api-availability-only">,
Flags<[HelpHidden, FrontendOption, NoInteractiveOption]>,
HelpText<"Only check the availability of the APIs, ignore function bodies">;

def unavailable_decl_optimization_EQ : Joined<["-"], "unavailable-decl-optimization=">,
MetaVarName<"<complete,none>">,
Flags<[FrontendOption, NoInteractiveOption]>,
HelpText<"Specify the optimization mode for unavailable declarations. The "
"value may be 'none' (no optimization) or 'complete' (code is not "
"generated at all unavailable declarations)">;

def library_level : Separate<["-"], "library-level">,
MetaVarName<"<level>">,
Flags<[HelpHidden, FrontendOption, ModuleInterfaceOption]>,
Expand Down
14 changes: 14 additions & 0 deletions include/swift/SIL/SILModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -1078,8 +1078,22 @@ namespace Lowering {
/// Determine whether the given class will be allocated/deallocated using the
/// Objective-C runtime, i.e., +alloc and -dealloc.
LLVM_LIBRARY_VISIBILITY bool usesObjCAllocator(ClassDecl *theClass);

/// Returns true if SIL/IR lowering for the given declaration should be skipped.
/// A declaration may not require lowering if, for example, it is annotated as
/// unavailable and optimization settings allow it to be omitted.
LLVM_LIBRARY_VISIBILITY bool shouldSkipLowering(Decl *D);
} // namespace Lowering

/// Apply the given function to each ABI member of \c D skipping the members
/// that should be skipped according to \c shouldSkipLowering()
template <typename F>
void forEachMemberToLower(IterableDeclContext *D, F &&f) {
for (auto *member : D->getABIMembers()) {
if (!Lowering::shouldSkipLowering(member))
f(member);
}
}
} // namespace swift

#endif
3 changes: 2 additions & 1 deletion include/swift/SIL/SILVTableVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,9 @@ template <class T> class SILVTableVisitor {
if (!theClass->hasKnownSwiftImplementation())
return;

for (auto member : theClass->getABIMembers())
forEachMemberToLower(theClass, [&](Decl *member) {
maybeAddMember(member);
});
}
};

Expand Down
6 changes: 6 additions & 0 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@ AvailabilityInference::parentDeclForInferredAvailability(const Decl *D) {
return NTD;
}

if (auto *PBD = dyn_cast<PatternBindingDecl>(D))
return PBD->getAnchoringVarDecl(0);

if (auto *OTD = dyn_cast<OpaqueTypeDecl>(D))
return OTD->getNamingDecl();

// Clang decls may be inaccurately parented rdar://53956555
if (D->hasClangNode())
return nullptr;
Expand Down
2 changes: 2 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,8 @@ AvailabilityContext Decl::getAvailabilityForLinkage() const {
return *containingContext;
}

// FIXME: Adopt AvailabilityInference::parentDeclForInferredAvailability()
// here instead of duplicating the logic.
if (auto *accessor = dyn_cast<AccessorDecl>(this))
return accessor->getStorage()->getAvailabilityForLinkage();

Expand Down
15 changes: 15 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,21 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
Args.hasArg(OPT_disable_availability_checking);
Opts.CheckAPIAvailabilityOnly |=
Args.hasArg(OPT_check_api_availability_only);

if (const Arg *A = Args.getLastArg(OPT_unavailable_decl_optimization_EQ)) {
auto value =
llvm::StringSwitch<Optional<UnavailableDeclOptimization>>(A->getValue())
.Case("none", UnavailableDeclOptimization::None)
.Case("complete", UnavailableDeclOptimization::Complete)
.Default(None);

if (value)
Opts.UnavailableDeclOptimizationMode = *value;
else
Diags.diagnose(SourceLoc(), diag::error_invalid_arg_value,
A->getAsString(Args), A->getValue());
}

Opts.WeakLinkAtTarget |= Args.hasArg(OPT_weak_link_at_target);

if (auto A = Args.getLastArg(OPT_enable_conformance_availability_errors,
Expand Down
7 changes: 7 additions & 0 deletions lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2456,9 +2456,13 @@ void swift::irgen::disableAddressSanitizer(IRGenModule &IGM, llvm::GlobalVariabl

/// Emit a global declaration.
void IRGenModule::emitGlobalDecl(Decl *D) {
if (Lowering::shouldSkipLowering(D))
return;

D->visitAuxiliaryDecls([&](Decl *decl) {
emitGlobalDecl(decl);
});

switch (D->getKind()) {
case DeclKind::Extension:
return emitExtension(cast<ExtensionDecl>(D));
Expand Down Expand Up @@ -5485,6 +5489,9 @@ Address IRGenModule::getAddrOfEnumCase(EnumElementDecl *Case,

void IRGenModule::emitNestedTypeDecls(DeclRange members) {
for (Decl *member : members) {
if (Lowering::shouldSkipLowering(member))
continue;

member->visitAuxiliaryDecls([&](Decl *decl) {
emitNestedTypeDecls({decl, nullptr});
});
Expand Down
3 changes: 3 additions & 0 deletions lib/IRGen/GenStruct.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1572,6 +1572,9 @@ void IRGenModule::emitStructDecl(StructDecl *st) {
}

void IRGenModule::maybeEmitOpaqueTypeDecl(OpaqueTypeDecl *opaque) {
if (Lowering::shouldSkipLowering(opaque))
return;

if (IRGen.Opts.EnableAnonymousContextMangledNames) {
// If we're emitting anonymous context mangled names for debuggability,
// then emit all opaque type descriptors and make them runtime-discoverable
Expand Down
3 changes: 3 additions & 0 deletions lib/IRGen/TBDGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,9 @@ void TBDGenVisitor::addSymbol(StringRef name, SymbolSource source,
}

bool TBDGenVisitor::willVisitDecl(Decl *D) {
if (Lowering::shouldSkipLowering(D))
return false;

// A @_silgen_name("...") function without a body only exists to
// forward-declare a symbol from another library.
if (auto AFD = dyn_cast<AbstractFunctionDecl>(D))
Expand Down
10 changes: 10 additions & 0 deletions lib/SIL/IR/SILModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -955,3 +955,13 @@ bool Lowering::usesObjCAllocator(ClassDecl *theClass) {
// allocation methods because they may have been overridden.
return theClass->getObjectModel() == ReferenceCounting::ObjC;
}

bool Lowering::shouldSkipLowering(Decl *D) {
if (D->getASTContext().LangOpts.UnavailableDeclOptimizationMode !=
UnavailableDeclOptimization::Complete)
return false;

// Unavailable declarations should be skipped if
// -unavailable-decl-optimization=complete is specified.
return D->getSemanticUnavailableAttr() != None;
}
7 changes: 7 additions & 0 deletions lib/SILGen/SILGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,13 @@ bool SILGenModule::hasFunction(SILDeclRef constant) {
return emittedFunctions.count(constant);
}

void SILGenModule::visit(Decl *D) {
if (Lowering::shouldSkipLowering(D))
return;

ASTVisitor::visit(D);
}

void SILGenModule::visitFuncDecl(FuncDecl *fd) { emitFunction(fd); }

void SILGenModule::emitFunctionDefinition(SILDeclRef constant, SILFunction *f) {
Expand Down
2 changes: 2 additions & 0 deletions lib/SILGen/SILGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
// Visitors for top-level forms
//===--------------------------------------------------------------------===//

void visit(Decl *D);

// These are either not allowed at global scope or don't require
// code emission.
void visitImportDecl(ImportDecl *d) {}
Expand Down
8 changes: 5 additions & 3 deletions lib/SILGen/SILGenType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1085,8 +1085,9 @@ class SILGenType : public TypeMemberVisitor<SILGenType> {
void emitType() {
SGM.emitLazyConformancesForType(theType);

for (Decl *member : theType->getABIMembers())
forEachMemberToLower(theType, [&](Decl *member) {
visit(member);
});

// Build a vtable if this is a class.
if (auto theClass = dyn_cast<ClassDecl>(theType)) {
Expand Down Expand Up @@ -1252,9 +1253,10 @@ class SILGenExtension : public TypeMemberVisitor<SILGenExtension> {
// @_objcImplementation extension, but we don't actually need to do any of
// the stuff that it currently does.

for (Decl *member : e->getABIMembers())
forEachMemberToLower(e, [&](Decl *member) {
visit(member);

});

// If this is a main-interface @_objcImplementation extension and the class
// has a synthesized destructor, emit it now.
if (auto cd = dyn_cast_or_null<ClassDecl>(e->getImplementedObjCDecl())) {
Expand Down
15 changes: 15 additions & 0 deletions test/IRGen/unavailable_decl_optimization.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// RUN: %target-swift-frontend -parse-as-library -module-name Test -validate-tbd-against-ir=missing %s -emit-ir | %FileCheck %s --check-prefixes=CHECK,CHECK-NO-STRIP

// RUN: %target-swift-frontend -parse-as-library -module-name Test -validate-tbd-against-ir=missing -unavailable-decl-optimization=complete %s -emit-ir | %FileCheck %s --check-prefixes=CHECK,CHECK-STRIP

// CHECK-NO-STRIP: s4Test14globalConstantSbvp
// CHECK-NO-STRIP: s4Test14globalConstantSbvau
// CHECK-STRIP-NOT: s4Test14globalConstantSbvp
// CHECK-STRIP-NOT: s4Test14globalConstantSbvau
@available(*, unavailable)
public let globalConstant = true

// CHECK-NO-STRIP: s4Test15unavailableFuncyyF
// CHECK-STRIP-NOT: s4Test15unavailableFuncyyF
@available(*, unavailable)
public func unavailableFunc() {}
55 changes: 55 additions & 0 deletions test/IRGen/unavailable_decl_optimization_class.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// RUN: %target-swift-frontend -parse-as-library -module-name Test -validate-tbd-against-ir=missing %s -emit-ir | %FileCheck %s --check-prefixes=CHECK,CHECK-NO-STRIP

// RUN: %target-swift-frontend -parse-as-library -module-name Test -validate-tbd-against-ir=missing -unavailable-decl-optimization=complete %s -emit-ir | %FileCheck %s --check-prefixes=CHECK,CHECK-STRIP

public class AvailableClass<T> {
// CHECK-NO-STRIP: s4Test14AvailableClassC19unavailablePropertyxvg
// CHECK-NO-STRIP: s4Test14AvailableClassC19unavailablePropertyxvs
// CHECK-NO-STRIP: s4Test14AvailableClassC19unavailablePropertyxvM
// CHECK-STRIP-NOT: s4Test14AvailableClassC19unavailablePropertyxvg
// CHECK-STRIP-NOT: s4Test14AvailableClassC19unavailablePropertyxvs
// CHECK-STRIP-NOT: s4Test14AvailableClassC19unavailablePropertyxvM
@available(*, unavailable)
public var unavailableProperty: T

// CHECK-NO-STRIP: s4Test14AvailableClassCyACyxGxcfC
// CHECK-NO-STRIP: s4Test14AvailableClassCyACyxGxcfc
// CHECK-STRIP-NOT: s4Test14AvailableClassCyACyxGxcfC
// CHECK-STRIP-NOT: s4Test14AvailableClassCyACyxGxcfc
@available(*, unavailable)
public init(_ t: T) { fatalError() }

// CHECK: s4Test14AvailableClassCfd
// CHECK: s4Test14AvailableClassCfD
deinit {}
}

@available(*, unavailable)
public class UnavailableClass<T> {
// CHECK-NO-STRIP: s4Test16UnavailableClassC8propertyxvg
// CHECK-NO-STRIP: s4Test16UnavailableClassC8propertyxvs
// CHECK-NO-STRIP: s4Test16UnavailableClassC8propertyxvM
// CHECK-STRIP-NOT: s4Test16UnavailableClassC8propertyxvg
// CHECK-STRIP-NOT: s4Test16UnavailableClassC8propertyxvs
// CHECK-STRIP-NOT: s4Test16UnavailableClassC8propertyxvM
public var property: T

// CHECK-NO-STRIP: s4Test16UnavailableClassCyACyxGxcfC
// CHECK-NO-STRIP: s4Test16UnavailableClassCyACyxGxcfc
// CHECK-STRIP-NOT: s4Test16UnavailableClassCyACyxGxcfC
// CHECK-STRIP-NOT: s4Test16UnavailableClassCyACyxGxcfc
public init(_ t: T) {
self.property = t
}

// CHECK-NO-STRIP: s4Test16UnavailableClassCfd
// CHECK-NO-STRIP: s4Test16UnavailableClassCfD
// CHECK-STRIP-NOT: s4Test16UnavailableClassCfd
// CHECK-STRIP-NOT: s4Test16UnavailableClassCfD
deinit {}
}

// CHECK: s4Test14AvailableClassCMa

// CHECK-NO-STRIP: s4Test16UnavailableClassCMa
// CHECK-STRIP-NOT: s4Test16UnavailableClassCMa
26 changes: 26 additions & 0 deletions test/IRGen/unavailable_decl_optimization_conformance.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// RUN: %target-swift-frontend -parse-as-library -module-name Test -validate-tbd-against-ir=missing %s -emit-ir | %FileCheck %s --check-prefixes=CHECK,CHECK-NO-STRIP

// RUN: %target-swift-frontend -parse-as-library -module-name Test -validate-tbd-against-ir=missing -unavailable-decl-optimization=complete %s -emit-ir | %FileCheck %s --check-prefixes=CHECK,CHECK-STRIP

// CHECK-NO-STRIP: s4Test1SVAA1PAAMc
// CHECK-STRIP-NOT: s4Test1SVAA1PAAMc

// CHECK-NO-STRIP: s4Test1SVAA1PAAWP
// CHECK-STRIP-NOT: s4Test1SVAA1PAAWP

// CHECK-NO-STRIP: s4Test1PMp
// CHECK-STRIP-NOT: s4Test1PMp

@available(*, unavailable)
public protocol P {
func requirement()
}

public struct S {}

@available(*, unavailable)
extension S: P {
// CHECK-NO-STRIP: s4Test1SV11requirementyyF
// CHECK-STRIP-NOT: s4Test1SV11requirementyyF
public func requirement() {}
}
56 changes: 56 additions & 0 deletions test/IRGen/unavailable_decl_optimization_enum.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// RUN: %target-swift-frontend -parse-as-library -module-name Test -validate-tbd-against-ir=missing %s -emit-ir | %FileCheck %s --check-prefixes=CHECK,CHECK-NO-STRIP

// RUN: %target-swift-frontend -parse-as-library -module-name Test -validate-tbd-against-ir=missing -unavailable-decl-optimization=complete %s -emit-ir | %FileCheck %s --check-prefixes=CHECK,CHECK-STRIP

// CHECK: private constant [27 x i8] c"availableEnumAvailableCase\00"

// FIXME: Should this reflection metadata for an unavailable case be stripped?
// CHECK: private constant [29 x i8] c"availableEnumUnavailableCase\00"

// CHECK-NO-STRIP: private constant [25 x i8] c"unavailableEnumFirstCase\00"
// CHECK-STRIP-NOT: private constant [25 x i8] c"unavailableEnumFirstCase\00"


public enum AvailableEnum {
case availableEnumAvailableCase

@available(*, unavailable)
case availableEnumUnavailableCase

// CHECK-NO-STRIP: s4Test13AvailableEnumO17unavailableMethodyyF
// CHECK-STRIP-NOT: s4Test13AvailableEnumO17unavailableMethodyyF
@available(*, unavailable)
public func unavailableMethod() {}

// CHECK: s4Test13AvailableEnumO21__derived_enum_equalsySbAC_ACtFZ
// CHECK: s4Test13AvailableEnumO4hash4intoys6HasherVz_tF
// CHECK: s4Test13AvailableEnumO9hashValueSivg
}

@available(*, unavailable)
public enum UnavailableEnum {
case unavailableEnumFirstCase

// CHECK-NO-STRIP: s4Test15UnavailableEnumO6methodyyF
// CHECK-STRIP-NOT: s4Test15UnavailableEnumO6methodyyF
public func method() {}

// CHECK-NO-STRIP: s4Test15UnavailableEnumO21__derived_enum_equalsySbAC_ACtFZ
// CHECK-STRIP-NOT: s4Test15UnavailableEnumO21__derived_enum_equalsySbAC_ACtFZ

// CHECK-NO-STRIP: s4Test15UnavailableEnumO4hash4intoys6HasherVz_tF
// CHECK-STRIP-NOT: s4Test15UnavailableEnumO4hash4intoys6HasherVz_tF

// CHECK-NO-STRIP: s4Test15UnavailableEnumO9hashValueSivg
// CHECK-STRIP-NOT: s4Test15UnavailableEnumO9hashValueSivg
}

// CHECK: s4Test13AvailableEnumOwug

// CHECK: s4Test13AvailableEnumOMa

// CHECK-NO-STRIP: s4Test15UnavailableEnumOwug
// CHECK-STRIP-NOT: s4Test15UnavailableEnumOwug

// CHECK-NO-STRIP: s4Test15UnavailableEnumOMa
// CHECK-STRIP-NOT: s4Test15UnavailableEnumOMa
Loading