Skip to content

Introduce -unavailable-decl-optimization #64644

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 4 commits into from
Mar 28, 2023
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 @@ -171,6 +187,10 @@ namespace swift {
/// Disable API availability checking.
bool DisableAvailabilityChecking = 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<"Deprecated, has no effect">;

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
3 changes: 3 additions & 0 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ AvailabilityInference::parentDeclForInferredAvailability(const Decl *D) {
return NTD;
}

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

// Clang decls may be inaccurately parented rdar://53956555
if (D->hasClangNode())
return nullptr;
Expand Down
14 changes: 14 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,20 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
Diags.diagnose(SourceLoc(), diag::warn_flag_deprecated,
"-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
4 changes: 4 additions & 0 deletions lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2458,9 +2458,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
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
194 changes: 194 additions & 0 deletions test/IRGen/unavailable_decl_optimization.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// 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() {}

@available(*, unavailable)
public struct UnavailableStruct<T> {
// CHECK-NO-STRIP: s4Test17UnavailableStructV8propertyxvg
// CHECK-NO-STRIP: s4Test17UnavailableStructV8propertyxvs
// CHECK-NO-STRIP: s4Test17UnavailableStructV8propertyxvM
// CHECK-STRIP-NOT: s4Test17UnavailableStructV8propertyxvg
// CHECK-STRIP-NOT: s4Test17UnavailableStructV8propertyxvs
// CHECK-STRIP-NOT: s4Test17UnavailableStructV8propertyxvM
public var property: T

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

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

@available(*, unavailable)
extension UnavailableStruct {
// CHECK-NO-STRIP: s4Test17UnavailableStructV15extensionMethodyyF
// CHECK-STRIP-NOT: s4Test17UnavailableStructV15extensionMethodyyF
public func extensionMethod() {}
}

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

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

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

@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 {}
}

public struct S<T> {
// CHECK-NO-STRIP: s4Test1SV19unavailablePropertyxvg
// CHECK-NO-STRIP: s4Test1SV19unavailablePropertyxvs
// CHECK-NO-STRIP: s4Test1SV19unavailablePropertyxvM
// CHECK-STRIP-NOT: s4Test1SV19unavailablePropertyxvg
// CHECK-STRIP-NOT: s4Test1SV19unavailablePropertyxvs
// CHECK-STRIP-NOT: s4Test1SV19unavailablePropertyxvM
@available(*, unavailable)
public var unavailableProperty: T

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

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

@available(*, unavailable)
extension S {
// CHECK-NO-STRIP: s4Test1SV28methodInUnavailableExtensionyyF
// CHECK-STRIP-NOT: s4Test1SV28methodInUnavailableExtensionyyF
public func methodInUnavailableExtension() {}
}

public enum E {
case a

@available(*, unavailable)
case b

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

public class C<T> {
// CHECK-NO-STRIP: s4Test1CC19unavailablePropertyxvg
// CHECK-NO-STRIP: s4Test1CC19unavailablePropertyxvs
// CHECK-NO-STRIP: s4Test1CC19unavailablePropertyxvM
// CHECK-STRIP-NOT: s4Test1CC19unavailablePropertyxvg
// CHECK-STRIP-NOT: s4Test1CC19unavailablePropertyxvs
// CHECK-STRIP-NOT: s4Test1CC19unavailablePropertyxvM
@available(*, unavailable)
public var unavailableProperty: T

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

// CHECK: s4Test1CCfd
// CHECK: s4Test1CCfD
deinit {}
}

public protocol P {
func requirement()
}

@available(*, unavailable)
extension S: P {
// CHECK-NO-STRIP: s4Test1SV11requirementyyF
// CHECK-STRIP-NOT: s4Test1SV11requirementyyF
public func requirement() {}
}

// CHECK-NO-STRIP: s4Test29unavailableFuncWithNestedTypeyyF
// CHECK-STRIP-NOT: s4Test29unavailableFuncWithNestedTypeyyF
@available(*, unavailable)
public func unavailableFuncWithNestedType() {
struct Nested {
// s4Test29unavailableFuncWithNestedTypeyyF0E0L_V6methodyyF
public func method() {}
}
}

// MARK: -

// MARK: UnavailableEnum

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

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

// MARK: UnavailableClass

// CHECK-NO-STRIP: s4Test16UnavailableClassCMa
// CHECK-STRIP-NOT: s4Test16UnavailableClassCMa

// MARK: E

// CHECK: s4Test1EOwug
// CHECK: s4Test1EOMa

// MARK: unavailableFuncWithNestedType().Nested

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