Skip to content

Initial support work for Package CMO #72249

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 1 commit into from
Mar 13, 2024
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
8 changes: 8 additions & 0 deletions include/swift/AST/SILOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ class SILOptions {
/// Controls whether cross module optimization is enabled.
CrossModuleOptimizationMode CMOMode = CrossModuleOptimizationMode::Off;

/// Optimization to perform default CMO within a package boundary.
/// Unlike the existing CMO, package CMO can be built with
/// -enable-library-evolution since package modules are required
/// to be built in the same project. To enable this optimization, the
/// module also needs to opt in to allow non-resilient access with
/// -experimental-allow-non-resilient-access.
bool EnableSerializePackage = true;

/// Enables the emission of stack protectors in functions.
bool EnableStackProtection = true;

Expand Down
4 changes: 4 additions & 0 deletions include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,10 @@ def Oplayground : Flag<["-"], "Oplayground">, Group<O_Group>,
Flags<[HelpHidden, FrontendOption, ModuleInterfaceOption]>,
HelpText<"Compile with optimizations appropriate for a playground">;

def ExperimentalPackageCMO : Flag<["-"], "experimental-package-cmo">,
Flags<[FrontendOption]>,
HelpText<"Enable optimization to perform defalut CMO within a package boundary">;

def EnbaleDefaultCMO : Flag<["-"], "enable-default-cmo">,
Flags<[HelpHidden, FrontendOption]>,
HelpText<"Perform conservative cross-module optimization">;
Expand Down
20 changes: 20 additions & 0 deletions include/swift/SIL/SILLinkage.h
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,26 @@ inline bool hasPublicVisibility(SILLinkage linkage) {
llvm_unreachable("Unhandled SILLinkage in switch.");
}

inline bool hasPublicOrPackageVisibility(SILLinkage linkage, bool includePackage) {
switch (linkage) {
case SILLinkage::Public:
case SILLinkage::PublicExternal:
case SILLinkage::PublicNonABI:
return true;
case SILLinkage::Package:
case SILLinkage::PackageExternal:
case SILLinkage::PackageNonABI:
return includePackage;
case SILLinkage::Hidden:
case SILLinkage::Shared:
case SILLinkage::Private:
case SILLinkage::HiddenExternal:
return false;
}

llvm_unreachable("Unhandled SILLinkage in switch.");
}

inline bool hasSharedVisibility(SILLinkage linkage) {
switch (linkage) {
case SILLinkage::Shared:
Expand Down
1 change: 1 addition & 0 deletions lib/Driver/ToolChains.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ void ToolChain::addCommonFrontendArgs(const OutputInfo &OI,
inputArgs.AddLastArg(arguments, options::OPT_suppress_warnings);
inputArgs.AddLastArg(arguments, options::OPT_suppress_remarks);
inputArgs.AddLastArg(arguments, options::OPT_experimental_package_bypass_resilience);
inputArgs.AddLastArg(arguments, options::OPT_ExperimentalPackageCMO);
inputArgs.AddLastArg(arguments, options::OPT_profile_generate);
inputArgs.AddLastArg(arguments, options::OPT_profile_use);
inputArgs.AddLastArg(arguments, options::OPT_profile_coverage_mapping);
Expand Down
12 changes: 12 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2335,6 +2335,18 @@ static bool ParseSILArgs(SILOptions &Opts, ArgList &Args,
} else if (Args.hasArg(OPT_EnbaleCMOEverything)) {
Opts.CMOMode = CrossModuleOptimizationMode::Everything;
}

if (Args.hasArg(OPT_ExperimentalPackageCMO)) {
if (!FEOpts.AllowNonResilientAccess) {
Diags.diagnose(SourceLoc(), diag::ignoring_option_requires_option,
"-experimental-package-cmo",
"-experimental-allow-non-resilient-access");
} else {
Opts.EnableSerializePackage = true;
Opts.CMOMode = CrossModuleOptimizationMode::Default;
}
}

Opts.EnableStackProtection =
Args.hasFlag(OPT_enable_stack_protector, OPT_disable_stack_protector,
Opts.EnableStackProtection);
Expand Down
6 changes: 4 additions & 2 deletions lib/IRGen/IRGenSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2446,8 +2446,10 @@ void IRGenModule::emitSILFunction(SILFunction *f) {
f->getLoweredFunctionType()->isPolymorphic())
return;

// Do not emit bodies of public_external functions.
if (hasPublicVisibility(f->getLinkage()) && f->isAvailableExternally())
// Do not emit bodies of public_external or package_external functions.
if (hasPublicOrPackageVisibility(f->getLinkage(),
f->getASTContext().SILOpts.EnableSerializePackage) &&
f->isAvailableExternally())
return;

PrettyStackTraceSILFunction stackTrace("emitting IR", f);
Expand Down
4 changes: 2 additions & 2 deletions lib/SIL/IR/SILFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -898,8 +898,8 @@ bool SILFunction::hasValidLinkageForFragileRef() const {
isAvailableExternally())
return false;

// Otherwise, only public functions can be referenced.
return hasPublicVisibility(getLinkage());
// Otherwise, only public or package functions can be referenced.
return hasPublicOrPackageVisibility(getLinkage(), getModule().getOptions().EnableSerializePackage);
}

bool
Expand Down
4 changes: 2 additions & 2 deletions lib/SIL/Verifier/SILVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2417,7 +2417,7 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
}
if (F.isSerialized()) {
require(RefG->isSerialized()
|| hasPublicVisibility(RefG->getLinkage()),
|| hasPublicOrPackageVisibility(RefG->getLinkage(), F.getModule().getOptions().EnableSerializePackage),
"alloc_global inside fragile function cannot "
"reference a private or hidden symbol");
}
Expand All @@ -2436,7 +2436,7 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
}
if (F.isSerialized()) {
require(RefG->isSerialized()
|| hasPublicVisibility(RefG->getLinkage()),
|| hasPublicOrPackageVisibility(RefG->getLinkage(), F.getModule().getOptions().EnableSerializePackage),
"global_addr/value inside fragile function cannot "
"reference a private or hidden symbol");
}
Expand Down
30 changes: 20 additions & 10 deletions lib/SILOptimizer/IPO/CrossModuleOptimization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,26 @@ class InstructionVisitor : public SILCloner<InstructionVisitor> {
}
};

static bool isVisible(SILLinkage linkage, SILOptions options) {
if (options.EnableSerializePackage)
return linkage == SILLinkage::Public || linkage == SILLinkage::Package;
return linkage == SILLinkage::Public;
}
static bool isVisible(AccessLevel accessLevel, SILOptions options) {
if (options.EnableSerializePackage)
return accessLevel == AccessLevel::Package || accessLevel == AccessLevel::Public;
return accessLevel == AccessLevel::Public;
}

/// Select functions in the module which should be serialized.
void CrossModuleOptimization::serializeFunctionsInModule() {

FunctionFlags canSerializeFlags;

// Start with public functions.
for (SILFunction &F : M) {
if (F.getLinkage() == SILLinkage::Public || everything) {
if (isVisible(F.getLinkage(), M.getOptions()) ||
everything) {
if (canSerializeFunction(&F, canSerializeFlags, /*maxDepth*/ 64)) {
serializeFunction(&F, canSerializeFlags);
}
Expand All @@ -185,7 +197,6 @@ bool CrossModuleOptimization::canSerializeFunction(
FunctionFlags &canSerializeFlags,
int maxDepth) {
auto iter = canSerializeFlags.find(function);

// Avoid infinite recursion in case it's a cycle in the call graph.
if (iter != canSerializeFlags.end())
return iter->second;
Expand Down Expand Up @@ -270,7 +281,7 @@ bool CrossModuleOptimization::canSerializeInstruction(SILInstruction *inst,
// function is completely inlined afterwards.
// Also, when emitting TBD files, we cannot introduce a new public symbol.
if ((conservative || M.getOptions().emitTBD) &&
!hasPublicVisibility(callee->getLinkage())) {
!hasPublicOrPackageVisibility(callee->getLinkage(), M.getOptions().EnableSerializePackage)) {
return false;
}

Expand All @@ -290,13 +301,12 @@ bool CrossModuleOptimization::canSerializeInstruction(SILInstruction *inst,
// inline.
if (!canUseFromInline(callee))
return false;

return true;
}
if (auto *GAI = dyn_cast<GlobalAddrInst>(inst)) {
SILGlobalVariable *global = GAI->getReferencedGlobal();
if ((conservative || M.getOptions().emitTBD) &&
!hasPublicVisibility(global->getLinkage())) {
!hasPublicOrPackageVisibility(global->getLinkage(), M.getOptions().EnableSerializePackage)) {
return false;
}

Expand Down Expand Up @@ -344,7 +354,7 @@ bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) {
// function is completely inlined afterwards.
// Also, when emitting TBD files, we cannot introduce a new public symbol.
if ((conservative || M.getOptions().emitTBD) &&
!hasPublicVisibility(referencedFunc->getLinkage())) {
!hasPublicOrPackageVisibility(referencedFunc->getLinkage(), M.getOptions().EnableSerializePackage)) {
return false;
}

Expand All @@ -368,7 +378,7 @@ bool CrossModuleOptimization::canSerializeType(SILType type) {
if (conservative && subNT->getEffectiveAccess() < AccessLevel::Package) {
return true;
}

// Exclude types which are defined in an @_implementationOnly imported
// module. Such modules are not transitively available.
if (!canUseFromInline(subNT)) {
Expand Down Expand Up @@ -542,15 +552,15 @@ void CrossModuleOptimization::serializeInstruction(SILInstruction *inst,
}
}
serializeFunction(callee, canSerializeFlags);
assert(callee->isSerialized() || callee->getLinkage() == SILLinkage::Public);
assert(callee->isSerialized() || isVisible(callee->getLinkage(), M.getOptions()));
return;
}
if (auto *GAI = dyn_cast<GlobalAddrInst>(inst)) {
SILGlobalVariable *global = GAI->getReferencedGlobal();
if (canSerializeGlobal(global)) {
serializeGlobal(global);
}
if (!hasPublicVisibility(global->getLinkage())) {
if (!hasPublicOrPackageVisibility(global->getLinkage(), M.getOptions().EnableSerializePackage)) {
global->setLinkage(SILLinkage::Public);
}
return;
Expand Down Expand Up @@ -606,7 +616,7 @@ void CrossModuleOptimization::makeDeclUsableFromInline(ValueDecl *decl) {
if (M.getSwiftModule() != decl->getDeclContext()->getParentModule())
return;

if (decl->getFormalAccess() < AccessLevel::Public &&
if (!isVisible(decl->getFormalAccess(), M.getOptions()) &&
!decl->isUsableFromInline()) {
// Mark the nominal type as "usableFromInline".
// TODO: find a way to do this without modifying the AST. The AST should be
Expand Down
1 change: 1 addition & 0 deletions lib/Serialization/DeserializeSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ static std::optional<SILLinkage> fromStableSILLinkage(unsigned value) {
case SIL_LINKAGE_SHARED: return SILLinkage::Shared;
case SIL_LINKAGE_PRIVATE: return SILLinkage::Private;
case SIL_LINKAGE_PUBLIC_EXTERNAL: return SILLinkage::PublicExternal;
case SIL_LINKAGE_PACKAGE_EXTERNAL: return SILLinkage::PackageExternal;
case SIL_LINKAGE_HIDDEN_EXTERNAL: return SILLinkage::HiddenExternal;
}

Expand Down
32 changes: 31 additions & 1 deletion test/SILOptimizer/Inputs/cross-module/default-module.swift
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@

import Submodule
import PrivateCModule

public func incrementByThree(_ x: Int) -> Int {
return incrementByOne(x) + 2
}

package func pkgFunc(_ x: Int) -> Int {
return subPkgFunc(x) + 2
}

public func incrementByThreeWithCall(_ x: Int) -> Int {
return incrementByOneNoCMO(x) + 2
}

package func pkgFuncNoCMO(_ x: Int) -> Int {
return subPkgFuncNoCMO(x) + 2
}

public func submoduleKlassMember() -> Int {
let k = SubmoduleKlass()
return k.i
}

package func pkgSubmoduleKlassMember() -> Int {
let k = PkgSubmoduleKlass()
return k.i
}

public final class ModuleKlass {
public var i: Int

Expand All @@ -28,11 +40,29 @@ public func moduleKlassMember() -> Int {
return k.i
}

package final class PkgModuleKlass {
package var i: Int

package init() {
i = 27
}
}

package func pkgModuleKlassMember() -> Int {
let k = PkgModuleKlass()
return k.i
}

public struct ModuleStruct {
public static var publicFunctionPointer: (Int) -> (Int) = incrementByThree
public static var privateFunctionPointer: (Int) -> (Int) = { $0 }
}

package struct PkgModuleStruct {
package static var funcPointer: (Int) -> (Int) = pkgFunc
package static var closurePointer: (Int) -> (Int) = { $0 }
}

public func callPrivateCFunc() -> Int {
return Int(privateCFunc())
}
Expand Down
16 changes: 16 additions & 0 deletions test/SILOptimizer/Inputs/cross-module/default-submodule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@ public func incrementByOne(_ x: Int) -> Int {
return x + 1
}

package func subPkgFunc(_ x: Int) -> Int {
return x + 1
}

@_semantics("optimize.no.crossmodule")
public func incrementByOneNoCMO(_ x: Int) -> Int {
return x + 1
}

@_semantics("optimize.no.crossmodule")
package func subPkgFuncNoCMO(_ x: Int) -> Int {
return x + 1
}

public final class SubmoduleKlass {
public var i: Int

Expand All @@ -16,3 +25,10 @@ public final class SubmoduleKlass {
}
}

package final class PkgSubmoduleKlass {
package var i: Int

package init() {
i = 27
}
}
8 changes: 4 additions & 4 deletions test/SILOptimizer/default-cmo.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@

// RUN: %empty-directory(%t)

// RUN: %target-build-swift -O -wmo -Xfrontend -enable-default-cmo -parse-as-library -emit-module -emit-module-path=%t/Submodule.swiftmodule -module-name=Submodule %S/Inputs/cross-module/default-submodule.swift -c -o %t/submodule.o
// RUN: %target-build-swift -O -wmo -Xfrontend -enable-default-cmo -parse-as-library -emit-module -emit-module-path=%t/Module.swiftmodule -module-name=Module -I%t -I%S/Inputs/cross-module %S/Inputs/cross-module/default-module.swift -c -o %t/module.o
// RUN: %target-build-swift -O -wmo -Xfrontend -enable-default-cmo -parse-as-library -emit-tbd -emit-tbd-path %t/ModuleTBD.tbd -emit-module -emit-module-path=%t/ModuleTBD.swiftmodule -module-name=ModuleTBD -I%t -I%S/Inputs/cross-module %S/Inputs/cross-module/default-module.swift -c -o %t/moduletbd.o -Xfrontend -tbd-install_name -Xfrontend module
// RUN: %target-build-swift -O -wmo -Xfrontend -enable-default-cmo -parse-as-library -emit-module -emit-module-path=%t/Submodule.swiftmodule -module-name=Submodule -package-name Pkg %S/Inputs/cross-module/default-submodule.swift -c -o %t/submodule.o
// RUN: %target-build-swift -O -wmo -Xfrontend -enable-default-cmo -parse-as-library -emit-module -emit-module-path=%t/Module.swiftmodule -module-name=Module -package-name Pkg -I%t -I%S/Inputs/cross-module %S/Inputs/cross-module/default-module.swift -c -o %t/module.o
// RUN: %target-build-swift -O -wmo -Xfrontend -enable-default-cmo -parse-as-library -emit-tbd -emit-tbd-path %t/ModuleTBD.tbd -emit-module -emit-module-path=%t/ModuleTBD.swiftmodule -module-name=ModuleTBD -package-name Pkg -I%t -I%S/Inputs/cross-module %S/Inputs/cross-module/default-module.swift -c -o %t/moduletbd.o -Xfrontend -tbd-install_name -Xfrontend module

// RUN: %target-build-swift -O -wmo -module-name=Main -I%t -I%S/Inputs/cross-module %s -emit-sil | %FileCheck %s
// RUN: %target-build-swift -O -wmo -module-name=Main -package-name Pkg -I%t -I%S/Inputs/cross-module %s -emit-sil | %FileCheck %s

// REQUIRES: swift_in_compiler

Expand Down
Loading