mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Cross module optimization
This is a first version of cross module optimization (CMO). The basic idea for CMO is to use the existing library evolution compiler features, but in an automated way. A new SIL module pass "annotates" functions and types with @inlinable and @usableFromInline. This results in functions being serialized into the swiftmodule file and thus available for optimizations in client modules. The annotation is done with a worklist-algorithm, starting from public functions and continuing with entities which are used from already selected functions. A heuristic performs a preselection on which functions to consider - currently just generic functions are selected. The serializer then writes annotated functions (including function bodies) into the swiftmodule file of the compiled module. Client modules are able to de-serialize such functions from their imported modules and use them for optimiations, like generic specialization. The optimization is gated by a new compiler option -cross-module-optimization (also available in the swift driver). By default this option is off. Without turning the option on, this change is (almost) a NFC. rdar://problem/22591518
This commit is contained in:
@@ -62,6 +62,9 @@ public:
|
||||
/// Useful when you want to enable -O LLVM opts but not -O SIL opts.
|
||||
bool DisableSILPerfOptimizations = false;
|
||||
|
||||
/// Controls whether cross module optimization is enabled.
|
||||
bool CrossModuleOptimization = false;
|
||||
|
||||
/// Controls whether or not paranoid verification checks are run.
|
||||
bool VerifyAll = false;
|
||||
|
||||
|
||||
@@ -589,6 +589,10 @@ def Oplayground : Flag<["-"], "Oplayground">, Group<O_Group>,
|
||||
Flags<[HelpHidden, FrontendOption, ModuleInterfaceOption]>,
|
||||
HelpText<"Compile with optimizations appropriate for a playground">;
|
||||
|
||||
def CrossModuleOptimization : Flag<["-"], "cross-module-optimization">,
|
||||
Flags<[HelpHidden, FrontendOption]>,
|
||||
HelpText<"Perform cross-module optimization">;
|
||||
|
||||
def RemoveRuntimeAsserts : Flag<["-"], "remove-runtime-asserts">,
|
||||
Flags<[FrontendOption]>,
|
||||
HelpText<"Remove runtime safety checks.">;
|
||||
|
||||
@@ -64,6 +64,8 @@ PASS(AccessEnforcementSelection, "access-enforcement-selection",
|
||||
"Access Enforcement Selection")
|
||||
PASS(AccessEnforcementWMO, "access-enforcement-wmo",
|
||||
"Access Enforcement Whole Module Optimization")
|
||||
PASS(CrossModuleSerializationSetup, "cross-module-serialization-setup",
|
||||
"Setup serialization flags for cross-module optimization")
|
||||
PASS(AccessSummaryDumper, "access-summary-dump",
|
||||
"Dump Address Parameter Access Summary")
|
||||
PASS(AccessedStorageDumper, "accessed-storage-dump",
|
||||
|
||||
@@ -2995,7 +2995,7 @@ SourceLoc ValueDecl::getAttributeInsertionLoc(bool forModifier) const {
|
||||
/// Returns true if \p VD needs to be treated as publicly-accessible
|
||||
/// at the SIL, LLVM, and machine levels due to being @usableFromInline.
|
||||
bool ValueDecl::isUsableFromInline() const {
|
||||
assert(getFormalAccess() == AccessLevel::Internal);
|
||||
assert(getFormalAccess() <= AccessLevel::Internal);
|
||||
|
||||
if (getAttrs().hasAttribute<UsableFromInlineAttr>() ||
|
||||
getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>() ||
|
||||
@@ -3092,7 +3092,7 @@ static AccessLevel getAdjustedFormalAccess(const ValueDecl *VD,
|
||||
return getMaximallyOpenAccessFor(VD);
|
||||
|
||||
if (treatUsableFromInlineAsPublic &&
|
||||
access == AccessLevel::Internal &&
|
||||
(access == AccessLevel::Internal || access == AccessLevel::Private) &&
|
||||
VD->isUsableFromInline()) {
|
||||
return AccessLevel::Public;
|
||||
}
|
||||
|
||||
@@ -400,6 +400,10 @@ ToolChain::constructInvocation(const CompileJobAction &job,
|
||||
Arguments.push_back("-module-name");
|
||||
Arguments.push_back(context.Args.MakeArgString(context.OI.ModuleName));
|
||||
|
||||
if (context.Args.hasArg(options::OPT_CrossModuleOptimization)) {
|
||||
Arguments.push_back("-cross-module-optimization");
|
||||
}
|
||||
|
||||
addOutputsOfType(Arguments, context.Output, context.Args,
|
||||
file_types::TY_OptRecord, "-save-optimization-record-path");
|
||||
|
||||
|
||||
@@ -871,6 +871,7 @@ static bool ParseSILArgs(SILOptions &Opts, ArgList &Args,
|
||||
Opts.EnableARCOptimizations &= !Args.hasArg(OPT_disable_arc_opts);
|
||||
Opts.EnableOSSAOptimizations &= !Args.hasArg(OPT_disable_ossa_opts);
|
||||
Opts.DisableSILPerfOptimizations |= Args.hasArg(OPT_disable_sil_perf_optzns);
|
||||
Opts.CrossModuleOptimization |= Args.hasArg(OPT_CrossModuleOptimization);
|
||||
Opts.VerifyAll |= Args.hasArg(OPT_sil_verify_all);
|
||||
Opts.DebugSerialization |= Args.hasArg(OPT_sil_debug_serialization);
|
||||
Opts.EmitVerboseSIL |= Args.hasArg(OPT_emit_verbose_sil);
|
||||
|
||||
@@ -1394,6 +1394,9 @@ static bool validateTBDIfNeeded(CompilerInvocation &Invocation,
|
||||
if (!astGuaranteedToCorrespondToSIL ||
|
||||
!inputFileKindCanHaveTBDValidated(Invocation.getInputKind()))
|
||||
return false;
|
||||
|
||||
if (Invocation.getSILOptions().CrossModuleOptimization)
|
||||
return false;
|
||||
|
||||
const auto &frontendOpts = Invocation.getFrontendOptions();
|
||||
auto mode = frontendOpts.ValidateTBDAgainstIR;
|
||||
|
||||
@@ -831,7 +831,8 @@ IRGenModule::getAddrOfParentContextDescriptor(DeclContext *from,
|
||||
|
||||
// Wrap up private types in an anonymous context for the containing file
|
||||
// unit so that the runtime knows they have unstable identity.
|
||||
if (!fromAnonymousContext && Type->isOutermostPrivateOrFilePrivateScope())
|
||||
if (!fromAnonymousContext && Type->isOutermostPrivateOrFilePrivateScope()
|
||||
&& !Type->isUsableFromInline())
|
||||
return {getAddrOfAnonymousContextDescriptor(Type),
|
||||
ConstantReference::Direct};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ silopt_register_sources(
|
||||
CapturePromotion.cpp
|
||||
CapturePropagation.cpp
|
||||
ClosureSpecializer.cpp
|
||||
CrossModuleSerializationSetup.cpp
|
||||
DeadFunctionElimination.cpp
|
||||
EagerSpecializer.cpp
|
||||
GlobalOpt.cpp
|
||||
|
||||
381
lib/SILOptimizer/IPO/CrossModuleSerializationSetup.cpp
Normal file
381
lib/SILOptimizer/IPO/CrossModuleSerializationSetup.cpp
Normal file
@@ -0,0 +1,381 @@
|
||||
//===--- UsePrespecialized.cpp - use pre-specialized functions ------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
/// An optimization which marks functions and types as inlinable or usable
|
||||
/// from inline. This lets such functions be serialized (later in the pipeline),
|
||||
/// which makes them available for other modules.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#define DEBUG_TYPE "cross-module-serialization-setup"
|
||||
#include "swift/SILOptimizer/PassManager/Passes.h"
|
||||
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
||||
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
|
||||
#include "swift/SIL/ApplySite.h"
|
||||
#include "swift/SIL/SILFunction.h"
|
||||
#include "swift/SIL/SILModule.h"
|
||||
#include "swift/SIL/SILCloner.h"
|
||||
#include "swift/AST/Module.h"
|
||||
#include "llvm/Support/Debug.h"
|
||||
|
||||
using namespace swift;
|
||||
|
||||
namespace {
|
||||
|
||||
/// Scans a whole module and marks functions and types as inlinable or usable
|
||||
/// from inline.
|
||||
class CrossModuleSerializationSetup {
|
||||
friend class InstructionVisitor;
|
||||
|
||||
// The worklist of function which should be serialized.
|
||||
llvm::SmallVector<SILFunction *, 16> workList;
|
||||
llvm::SmallPtrSet<SILFunction *, 16> functionsHandled;
|
||||
|
||||
llvm::SmallPtrSet<TypeBase *, 16> typesHandled;
|
||||
|
||||
SILModule &M;
|
||||
|
||||
void addToWorklistIfNotHandled(SILFunction *F) {
|
||||
if (functionsHandled.count(F) == 0) {
|
||||
workList.push_back(F);
|
||||
functionsHandled.insert(F);
|
||||
}
|
||||
}
|
||||
|
||||
bool setUpForSerialization(SILFunction *F);
|
||||
|
||||
void prepareInstructionForSerialization(SILInstruction *inst);
|
||||
|
||||
void handleReferencedFunction(SILFunction *F);
|
||||
|
||||
void handleReferencedMethod(SILDeclRef method);
|
||||
|
||||
void makeTypeUsableFromInline(CanType type);
|
||||
|
||||
void makeSubstUsableFromInline(const SubstitutionMap &substs);
|
||||
|
||||
bool markAsEmitIntoClient(SILFunction *F);
|
||||
|
||||
public:
|
||||
CrossModuleSerializationSetup(SILModule &M) : M(M) { }
|
||||
|
||||
void scanModule();
|
||||
};
|
||||
|
||||
static bool canUseFromInline(SILFunction *F) {
|
||||
if (!F)
|
||||
return false;
|
||||
|
||||
switch (F->getLinkage()) {
|
||||
case SILLinkage::PublicNonABI:
|
||||
case SILLinkage::Shared:
|
||||
return F->isSerialized() != IsNotSerialized;
|
||||
case SILLinkage::Public:
|
||||
case SILLinkage::Hidden:
|
||||
case SILLinkage::Private:
|
||||
case SILLinkage::PublicExternal:
|
||||
case SILLinkage::SharedExternal:
|
||||
case SILLinkage::PrivateExternal:
|
||||
case SILLinkage::HiddenExternal:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Visitor for making used types of an intruction inlinable.
|
||||
///
|
||||
/// We use the SILCloner for visiting types, though it sucks that we allocate
|
||||
/// instructions just to delete them immediately. But it's better than to
|
||||
/// reimplement the logic.
|
||||
/// TODO: separate the type visiting logic in SILCloner from the instruction
|
||||
/// creation.
|
||||
class InstructionVisitor : public SILCloner<InstructionVisitor> {
|
||||
friend class SILCloner<InstructionVisitor>;
|
||||
friend class SILInstructionVisitor<InstructionVisitor>;
|
||||
|
||||
private:
|
||||
CrossModuleSerializationSetup &CMS;
|
||||
|
||||
public:
|
||||
InstructionVisitor(SILFunction *F, CrossModuleSerializationSetup &CMS) :
|
||||
SILCloner(*F), CMS(CMS) {}
|
||||
|
||||
SILType remapType(SILType Ty) {
|
||||
CMS.makeTypeUsableFromInline(Ty.getASTType());
|
||||
return Ty;
|
||||
}
|
||||
|
||||
CanType remapASTType(CanType Ty) {
|
||||
CMS.makeTypeUsableFromInline(Ty);
|
||||
return Ty;
|
||||
}
|
||||
|
||||
SubstitutionMap remapSubstitutionMap(SubstitutionMap Subs) {
|
||||
CMS.makeSubstUsableFromInline(Subs);
|
||||
return Subs;
|
||||
}
|
||||
|
||||
void postProcess(SILInstruction *Orig, SILInstruction *Cloned) {
|
||||
SILInstruction::destroy(Cloned);
|
||||
Orig->getFunction()->getModule().deallocateInst(Cloned);
|
||||
}
|
||||
|
||||
SILValue getMappedValue(SILValue Value) { return Value; }
|
||||
|
||||
SILBasicBlock *remapBasicBlock(SILBasicBlock *BB) { return BB; }
|
||||
|
||||
static void visitInst(SILInstruction *I, CrossModuleSerializationSetup &CMS) {
|
||||
InstructionVisitor visitor(I->getFunction(), CMS);
|
||||
visitor.visit(I);
|
||||
}
|
||||
};
|
||||
|
||||
/// Make a nominal type, including it's context, usable from inline.
|
||||
static void makeNominalUsableFromInline(NominalTypeDecl *NT, SILModule &M) {
|
||||
if (NT->getEffectiveAccess() >= AccessLevel::Public)
|
||||
return;
|
||||
|
||||
if (!NT->isUsableFromInline()) {
|
||||
// Mark the nominal type as "usableFromInline".
|
||||
// TODO: find a way to do this without modifying the AST. The AST should be
|
||||
// immutable at this point.
|
||||
auto &ctx = NT->getASTContext();
|
||||
auto *attr = new (ctx) UsableFromInlineAttr(/*implicit=*/true);
|
||||
NT->getAttrs().add(attr);
|
||||
}
|
||||
if (auto *enclosingNominal = dyn_cast<NominalTypeDecl>(NT->getDeclContext())) {
|
||||
makeNominalUsableFromInline(enclosingNominal, M);
|
||||
} else if (auto *enclosingExt = dyn_cast<ExtensionDecl>(NT->getDeclContext())) {
|
||||
if (auto *extendedNominal = enclosingExt->getExtendedNominal()) {
|
||||
makeNominalUsableFromInline(extendedNominal, M);
|
||||
}
|
||||
} else if (NT->getDeclContext()->isLocalContext()) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure that the \p type is usable from serialized functions.
|
||||
void CrossModuleSerializationSetup::makeTypeUsableFromInline(CanType type) {
|
||||
if (!typesHandled.insert(type.getPointer()).second)
|
||||
return;
|
||||
|
||||
if (NominalTypeDecl *NT = type->getNominalOrBoundGenericNominal()) {
|
||||
makeNominalUsableFromInline(NT, M);
|
||||
}
|
||||
|
||||
// Also make all sub-types usable from inline.
|
||||
type.visit([this](Type rawSubType) {
|
||||
CanType subType = rawSubType->getCanonicalType();
|
||||
if (typesHandled.insert(subType.getPointer()).second) {
|
||||
if (NominalTypeDecl *subNT = subType->getNominalOrBoundGenericNominal()) {
|
||||
makeNominalUsableFromInline(subNT, M);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Ensure that all replacement types of \p substs are usable from serialized
|
||||
/// functions.
|
||||
void CrossModuleSerializationSetup::
|
||||
makeSubstUsableFromInline(const SubstitutionMap &substs) {
|
||||
for (Type replType : substs.getReplacementTypes()) {
|
||||
makeTypeUsableFromInline(replType->getCanonicalType());
|
||||
}
|
||||
}
|
||||
|
||||
/// Decide whether to serialize a function.
|
||||
static bool shouldSerialize(SILFunction *F) {
|
||||
// The basic heursitic: serialize all generic functions, because it makes a
|
||||
// huge difference if generic functions can be specialized or not.
|
||||
if (!F->getLoweredFunctionType()->isPolymorphic())
|
||||
return false;
|
||||
|
||||
// Check if we already handled this function before.
|
||||
if (F->isSerialized() == IsSerialized)
|
||||
return false;
|
||||
|
||||
if (F->hasSemanticsAttr("optimize.no.crossmodule"))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void makeFunctionUsableFromInline(SILFunction *F) {
|
||||
if (!isAvailableExternally(F->getLinkage()))
|
||||
F->setLinkage(SILLinkage::Public);
|
||||
}
|
||||
|
||||
/// Prepare \p inst for serialization and in case it's a function_ref, put the
|
||||
/// referenced function onto the worklist.
|
||||
void CrossModuleSerializationSetup::
|
||||
prepareInstructionForSerialization(SILInstruction *inst) {
|
||||
// Make all types of the instruction usable from inline.
|
||||
InstructionVisitor::visitInst(inst, *this);
|
||||
|
||||
// Put callees onto the worklist if they should be serialized as well.
|
||||
if (auto *FRI = dyn_cast<FunctionRefBaseInst>(inst)) {
|
||||
SILFunction *callee = FRI->getReferencedFunctionOrNull();
|
||||
assert(callee);
|
||||
handleReferencedFunction(callee);
|
||||
return;
|
||||
}
|
||||
if (auto *MI = dyn_cast<MethodInst>(inst)) {
|
||||
handleReferencedMethod(MI->getMember());
|
||||
return;
|
||||
}
|
||||
if (auto *KPI = dyn_cast<KeyPathInst>(inst)) {
|
||||
KPI->getPattern()->visitReferencedFunctionsAndMethods(
|
||||
[this](SILFunction *func) { handleReferencedFunction(func); },
|
||||
[this](SILDeclRef method) { handleReferencedMethod(method); });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CrossModuleSerializationSetup::handleReferencedFunction(SILFunction *func) {
|
||||
if (!func->isDefinition() || func->isAvailableExternally())
|
||||
return;
|
||||
if (shouldSerialize(func)) {
|
||||
addToWorklistIfNotHandled(func);
|
||||
} else {
|
||||
makeFunctionUsableFromInline(func);
|
||||
}
|
||||
}
|
||||
|
||||
void CrossModuleSerializationSetup::handleReferencedMethod(SILDeclRef method) {
|
||||
if (method.isForeign)
|
||||
return;
|
||||
// Prevent the method from dead-method elimination.
|
||||
auto *methodDecl = cast<AbstractFunctionDecl>(method.getDecl());
|
||||
M.addExternallyVisibleDecl(getBaseMethod(methodDecl));
|
||||
}
|
||||
|
||||
/// Setup the function \p param F for serialization and put callees onto the
|
||||
/// worklist for further processing.
|
||||
///
|
||||
/// Returns false in case this is not possible for some reason.
|
||||
bool CrossModuleSerializationSetup::setUpForSerialization(SILFunction *F) {
|
||||
// First step: check if serializing F is even possible.
|
||||
for (SILBasicBlock &block : *F) {
|
||||
for (SILInstruction &inst : block) {
|
||||
if (auto *FRI = dyn_cast<FunctionRefBaseInst>(&inst)) {
|
||||
SILFunction *callee = FRI->getReferencedFunctionOrNull();
|
||||
if (!canUseFromInline(callee))
|
||||
return false;
|
||||
} else if (auto *KPI = dyn_cast<KeyPathInst>(&inst)) {
|
||||
bool canUse = true;
|
||||
KPI->getPattern()->visitReferencedFunctionsAndMethods(
|
||||
[&](SILFunction *func) {
|
||||
if (!canUseFromInline(func))
|
||||
canUse = false;
|
||||
},
|
||||
[](SILDeclRef method) { });
|
||||
if (!canUse)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second step: go through all instructions and prepare them for
|
||||
// for serialization.
|
||||
for (SILBasicBlock &block : *F) {
|
||||
for (SILInstruction &inst : block) {
|
||||
prepareInstructionForSerialization(&inst);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Select functions in the module which should be serialized.
|
||||
void CrossModuleSerializationSetup::scanModule() {
|
||||
|
||||
// Start with public functions.
|
||||
for (SILFunction &F : M) {
|
||||
if (F.getLinkage() == SILLinkage::Public)
|
||||
addToWorklistIfNotHandled(&F);
|
||||
}
|
||||
|
||||
// Continue with called functions.
|
||||
while (!workList.empty()) {
|
||||
SILFunction *F = workList.pop_back_val();
|
||||
// Decide whether we want to serialize the function.
|
||||
if (shouldSerialize(F)) {
|
||||
// Try to serialize.
|
||||
if (setUpForSerialization(F)) {
|
||||
F->setSerialized(IsSerialized);
|
||||
|
||||
// As a code size optimization, make serialized functions
|
||||
// @alwaysEmitIntoClient.
|
||||
if (!markAsEmitIntoClient(F)) {
|
||||
// We don't have a declaration to put the attribute on (e.g. in case
|
||||
// the function is a closure). Just make the function public instead
|
||||
// of @alwaysEmitIntoClient.
|
||||
F->setLinkage(SILLinkage::Public);
|
||||
}
|
||||
} else {
|
||||
// If for some reason the function cannot be serialized, we mark it as
|
||||
// usable-from-inline.
|
||||
makeFunctionUsableFromInline(F);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks a function as @alwaysEmitIntoClient and returns true if this is
|
||||
/// successful.
|
||||
bool CrossModuleSerializationSetup::markAsEmitIntoClient(SILFunction *F) {
|
||||
auto *DC = F->getDeclContext();
|
||||
if (!DC)
|
||||
return false;
|
||||
|
||||
Decl *decl = DC->getAsDecl();
|
||||
if (!decl)
|
||||
return false;
|
||||
|
||||
if (!isa<AbstractFunctionDecl>(decl))
|
||||
return false;
|
||||
|
||||
F->setLinkage(SILLinkage::PublicNonABI);
|
||||
|
||||
// Adding the attribute is only needed to be able to compile the
|
||||
// client module with -Onone. For optimized builds, setting the
|
||||
// SILLinkage is enough, because with optimization, the client module
|
||||
// eagerly de-serializes all functions and therefore gets the
|
||||
// linkage right. But with -Onone, the linkage is derived purly from
|
||||
// the AST.
|
||||
// TODO: also here, we should find a way to not modify the AST.
|
||||
auto &ctx = M.getASTContext();
|
||||
auto *attr = new (ctx) AlwaysEmitIntoClientAttr(/*implicit=*/true);
|
||||
decl->getAttrs().add(attr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class CrossModuleSerializationSetupPass: public SILModuleTransform {
|
||||
void run() override {
|
||||
|
||||
auto &M = *getModule();
|
||||
if (M.getSwiftModule()->isResilient())
|
||||
return;
|
||||
if (!M.isWholeModule())
|
||||
return;
|
||||
if (!M.getOptions().CrossModuleOptimization)
|
||||
return;
|
||||
|
||||
CrossModuleSerializationSetup CMSS(M);
|
||||
CMSS.scanModule();
|
||||
}
|
||||
};
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
SILTransform *swift::createCrossModuleSerializationSetup() {
|
||||
return new CrossModuleSerializationSetupPass();
|
||||
}
|
||||
@@ -411,6 +411,8 @@ static void addPerfEarlyModulePassPipeline(SILPassPipelinePlan &P) {
|
||||
|
||||
// Add the outliner pass (Osize).
|
||||
P.addOutliner();
|
||||
|
||||
P.addCrossModuleSerializationSetup();
|
||||
}
|
||||
|
||||
static void addHighLevelEarlyLoopOptPipeline(SILPassPipelinePlan &P) {
|
||||
|
||||
@@ -576,7 +576,7 @@ SILDeserializer::readSILFunctionChecked(DeclID FID, SILFunction *existingFn,
|
||||
// PublicNonABI function, which has HiddenExternal when
|
||||
// referenced as a declaration, and SharedExternal when it has
|
||||
// a deserialized body.
|
||||
if (fn->getLinkage() == SILLinkage::HiddenExternal &&
|
||||
if (isAvailableExternally(fn->getLinkage()) &&
|
||||
linkage == SILLinkage::PublicNonABI) {
|
||||
fn->setLinkage(SILLinkage::SharedExternal);
|
||||
}
|
||||
|
||||
204
test/SILOptimizer/Inputs/cross-module.swift
Normal file
204
test/SILOptimizer/Inputs/cross-module.swift
Normal file
@@ -0,0 +1,204 @@
|
||||
import Submodule
|
||||
|
||||
private enum PE<T> {
|
||||
case A
|
||||
case B(T)
|
||||
}
|
||||
|
||||
public struct Container {
|
||||
|
||||
private final class Base {
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
public func testclass<T>(_ t: T) -> T {
|
||||
var arr = Array<Base>()
|
||||
arr.append(Base())
|
||||
print(arr)
|
||||
return t
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
@_semantics("optimize.sil.specialize.generic.never")
|
||||
public func testclass_gen<T>(_ t: T) -> T {
|
||||
var arr = Array<Base>()
|
||||
arr.append(Base())
|
||||
print(arr)
|
||||
return t
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
public func testenum<T>(_ t: T) -> T {
|
||||
var arr = Array<PE<T>>()
|
||||
arr.append(.B(t))
|
||||
print(arr)
|
||||
return t
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
@_semantics("optimize.sil.specialize.generic.never")
|
||||
public func testenum_gen<T>(_ t: T) -> T {
|
||||
var arr = Array<PE<T>>()
|
||||
arr.append(.B(t))
|
||||
print(arr)
|
||||
return t
|
||||
}
|
||||
|
||||
public init() { }
|
||||
}
|
||||
|
||||
private class PrivateBase<T> {
|
||||
var t: T
|
||||
func foo() -> Int { return 27 }
|
||||
|
||||
init(_ t: T) { self.t = t }
|
||||
}
|
||||
|
||||
private class PrivateDerived<T> : PrivateBase<T> {
|
||||
override func foo() -> Int { return 28 }
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
private func getClass<T>(_ t : T) -> PrivateBase<T> {
|
||||
return PrivateDerived<T>(t)
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
public func createClass<T>(_ t: T) -> Int {
|
||||
return getClass(t).foo()
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
@_semantics("optimize.sil.specialize.generic.never")
|
||||
public func createClass_gen<T>(_ t: T) -> Int {
|
||||
return getClass(t).foo()
|
||||
}
|
||||
|
||||
private struct PrivateError: Error { }
|
||||
|
||||
public func returnPrivateError<V>(_ v: V) -> Error {
|
||||
return PrivateError()
|
||||
}
|
||||
|
||||
struct InternalError: Error { }
|
||||
|
||||
public func returnInternalError<V>(_ v: V) -> Error {
|
||||
return InternalError()
|
||||
}
|
||||
|
||||
private protocol PrivateProtocol {
|
||||
func foo() -> Int
|
||||
}
|
||||
|
||||
open class OpenClass<T> {
|
||||
public init() { }
|
||||
}
|
||||
|
||||
extension OpenClass {
|
||||
@inline(never)
|
||||
public func testit() -> Bool {
|
||||
return self is PrivateProtocol
|
||||
}
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
public func checkIfClassConforms<T>(_ t: T) {
|
||||
let x = OpenClass<T>()
|
||||
print(x.testit())
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
@_semantics("optimize.sil.specialize.generic.never")
|
||||
public func checkIfClassConforms_gen<T>(_ t: T) {
|
||||
let x = OpenClass<T>()
|
||||
print(x.testit())
|
||||
}
|
||||
|
||||
extension Int : PrivateProtocol {
|
||||
func foo() -> Int { return self }
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
private func printFooExistential(_ p: PrivateProtocol) {
|
||||
print(p.foo())
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
private func printFooGeneric<T: PrivateProtocol>(_ p: T) {
|
||||
print(p.foo())
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
public func callFoo<T>(_ t: T) {
|
||||
printFooExistential(123)
|
||||
printFooGeneric(1234)
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
@_semantics("optimize.sil.specialize.generic.never")
|
||||
public func callFoo_gen<T>(_ t: T) {
|
||||
printFooExistential(123)
|
||||
printFooGeneric(1234)
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
public func callGenericSubmoduleFunc<T>(_ t: T) {
|
||||
genericSubmoduleFunc(t)
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
@_semantics("optimize.sil.specialize.generic.never")
|
||||
public func callGenericSubmoduleFunc_gen<T>(_ t: T) {
|
||||
genericSubmoduleFunc(t)
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
public func genericClosure<T>(_ t: T) -> T {
|
||||
let c : () -> T = { return t }
|
||||
return c()
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
@_semantics("optimize.sil.specialize.generic.never")
|
||||
public func genericClosure_gen<T>(_ t: T) -> T {
|
||||
let c : () -> T = { return t }
|
||||
return c()
|
||||
}
|
||||
|
||||
struct Abc {
|
||||
var x: Int { return 27 }
|
||||
var y: Int { return 28 }
|
||||
}
|
||||
|
||||
class Myclass {
|
||||
var x: Int { return 27 }
|
||||
var y: Int { return 28 }
|
||||
}
|
||||
|
||||
class Derived : Myclass {
|
||||
override var x: Int { return 29 }
|
||||
override var y: Int { return 30 }
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
func getStructKeypath<T>(_ t: T) -> KeyPath<Abc, Int> {
|
||||
return \Abc.x
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
public func useStructKeypath<T>(_ t: T) -> Int {
|
||||
let abc = Abc()
|
||||
return abc[keyPath: getStructKeypath(t)]
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
func getClassKeypath<T>(_ t: T) -> KeyPath<Myclass, Int> {
|
||||
return \Myclass.x
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
public func useClassKeypath<T>(_ t: T) -> Int {
|
||||
let c = Derived()
|
||||
return c[keyPath: getClassKeypath(t)]
|
||||
}
|
||||
|
||||
12
test/SILOptimizer/Inputs/cross-submodule.swift
Normal file
12
test/SILOptimizer/Inputs/cross-submodule.swift
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
@inline(never)
|
||||
@_semantics("optimize.no.crossmodule")
|
||||
private func printit(_ x: Any) {
|
||||
print(x)
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
public func genericSubmoduleFunc<T>(_ t: T) {
|
||||
printit(t)
|
||||
}
|
||||
|
||||
117
test/SILOptimizer/cross-module-optimization.swift
Normal file
117
test/SILOptimizer/cross-module-optimization.swift
Normal file
@@ -0,0 +1,117 @@
|
||||
// First test: functional correctness
|
||||
|
||||
// RUN: %empty-directory(%t)
|
||||
// RUN: %target-build-swift -O -wmo -parse-as-library -cross-module-optimization -emit-module -emit-module-path=%t/Submodule.swiftmodule -module-name=Submodule %S/Inputs/cross-submodule.swift -c -o %t/submodule.o
|
||||
// RUN: %target-build-swift -O -wmo -parse-as-library -cross-module-optimization -emit-module -emit-module-path=%t/Test.swiftmodule -module-name=Test -I%t %S/Inputs/cross-module.swift -c -o %t/test.o
|
||||
// RUN: %target-build-swift -O -wmo -module-name=Main -I%t %s -c -o %t/main.o
|
||||
// RUN: %target-swiftc_driver %t/main.o %t/test.o %t/submodule.o -o %t/a.out
|
||||
// RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT
|
||||
|
||||
// Check if it also works if the main module is compiled with -Onone:
|
||||
|
||||
// RUN: %target-build-swift -Onone -wmo -module-name=Main -I%t %s -c -o %t/main-onone.o
|
||||
// RUN: %target-swiftc_driver %t/main-onone.o %t/test.o %t/submodule.o -o %t/a.out
|
||||
// RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT
|
||||
|
||||
// REQUIRES: executable_test
|
||||
|
||||
// Second test: check if CMO really imports the SIL of functions in other modules.
|
||||
|
||||
// RUN: %target-build-swift -O -wmo -module-name=Main -I%t %s -Xllvm -sil-disable-pass=FunctionSignatureOpts -emit-sil | %FileCheck %s -check-prefix=CHECK-SIL
|
||||
|
||||
|
||||
import Test
|
||||
|
||||
|
||||
func testNestedTypes() {
|
||||
let c = Container()
|
||||
|
||||
// CHECK-OUTPUT: [Test.Container.Base]
|
||||
// CHECK-OUTPUT: 27
|
||||
// CHECK-SIL-DAG: sil shared [noinline] @$s4Test9ContainerV9testclassyxxlFSi_Tg5
|
||||
print(c.testclass(27))
|
||||
// CHECK-OUTPUT: [Test.Container.Base]
|
||||
// CHECK-OUTPUT: 27
|
||||
// CHECK-SIL-DAG: sil shared_external {{.*}} @$s4Test9ContainerV13testclass_genyxxlF
|
||||
print(c.testclass_gen(27))
|
||||
// CHECK-OUTPUT: [Test.PE<Swift.Int>.B(27)]
|
||||
// CHECK-OUTPUT: 27
|
||||
// CHECK-SIL-DAG: sil shared [noinline] @$s4Test9ContainerV8testenumyxxlFSi_Tg5
|
||||
print(c.testenum(27))
|
||||
// CHECK-OUTPUT: [Test.PE<Swift.Int>.B(27)]
|
||||
// CHECK-OUTPUT: 27
|
||||
// CHECK-SIL-DAG: sil shared_external {{.*}} @$s4Test9ContainerV12testenum_genyxxlF
|
||||
print(c.testenum_gen(27))
|
||||
}
|
||||
|
||||
|
||||
func testClass() {
|
||||
// CHECK-OUTPUT: 28
|
||||
// CHECK-SIL-DAG: sil shared [noinline] @$s4Test11createClassySixlFSi_Tg5
|
||||
// CHECK-SIL-DAG: sil shared [noinline] @${{.*Test.*getClass}}
|
||||
print(createClass(0))
|
||||
// CHECK-OUTPUT: 28
|
||||
// CHECK-SIL-DAG: sil shared_external {{.*}} @$s4Test15createClass_genySixlF
|
||||
print(createClass_gen(0))
|
||||
}
|
||||
|
||||
func testError() {
|
||||
// CHECK-OUTPUT: PrivateError()
|
||||
// CHECK-SIL-DAG: sil @$s4Test12PrivateError33_{{.*}} : $@convention(method) (@thin PrivateError.Type) -> PrivateError{{$}}
|
||||
print(returnPrivateError(27))
|
||||
// CHECK-OUTPUT: InternalError()
|
||||
// CHECK-SIL-DAG: sil @$s4Test13InternalErrorVACycfC : $@convention(method) (@thin InternalError.Type) -> InternalError{{$}}
|
||||
print(returnInternalError(27))
|
||||
}
|
||||
|
||||
func testProtocol() {
|
||||
// CHECK-OUTPUT: false
|
||||
// CHECK-SIL-DAG: sil shared [noinline] @$s4Test20checkIfClassConformsyyxlFSi_Tg5
|
||||
checkIfClassConforms(27)
|
||||
// CHECK-OUTPUT: false
|
||||
// CHECK-SIL-DAG: sil shared_external {{.*}} @$s4Test24checkIfClassConforms_genyyxlF
|
||||
checkIfClassConforms_gen(27)
|
||||
// CHECK-OUTPUT: 123
|
||||
// CHECK-OUTPUT: 1234
|
||||
// CHECK-SIL-DAG: sil shared [noinline] @$s4Test7callFooyyxlFSi_Tg5
|
||||
// CHECK-SIL-DAG: sil [{{.*}}] @$s4Test19printFooExistential33_{{.*}} : $@convention(thin) (@in_guaranteed PrivateProtocol) -> (){{$}}
|
||||
callFoo(27)
|
||||
// CHECK-OUTPUT: 123
|
||||
// CHECK-OUTPUT: 1234
|
||||
// CHECK-SIL-DAG: sil shared_external {{.*}} @$s4Test11callFoo_genyyxlF
|
||||
callFoo_gen(27)
|
||||
}
|
||||
|
||||
func testSubModule() {
|
||||
// CHECK-OUTPUT: 10
|
||||
// CHECK-SIL-DAG: sil shared [noinline] @$s4Test24callGenericSubmoduleFuncyyxlFSi_Tg5
|
||||
// CHECK-SIL-DAG: sil shared [noinline] @$s9Submodule07genericA4FuncyyxlF
|
||||
callGenericSubmoduleFunc(10)
|
||||
// CHECK-OUTPUT: 101
|
||||
// CHECK-SIL-DAG: sil shared_external {{.*}} @$s4Test28callGenericSubmoduleFunc_genyyxlF
|
||||
callGenericSubmoduleFunc_gen(101)
|
||||
}
|
||||
|
||||
func testClosures() {
|
||||
// CHECK-OUTPUT: 23
|
||||
// CHECK-SIL-DAG: sil shared [noinline] @$s4Test14genericClosureyxxlFSi_Tg5
|
||||
print(genericClosure(23))
|
||||
// CHECK-OUTPUT: 24
|
||||
// CHECK-SIL-DAG: sil shared_external {{.*}} @$s4Test18genericClosure_genyxxlF
|
||||
print(genericClosure_gen(24))
|
||||
}
|
||||
|
||||
func testKeypath() {
|
||||
// CHECK-OUTPUT: 27
|
||||
print(useStructKeypath(0))
|
||||
// CHECK-OUTPUT: 29
|
||||
print(useClassKeypath(0))
|
||||
}
|
||||
|
||||
testNestedTypes()
|
||||
testClass()
|
||||
testError()
|
||||
testProtocol()
|
||||
testSubModule()
|
||||
testClosures()
|
||||
testKeypath()
|
||||
Reference in New Issue
Block a user