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:
Erik Eckstein
2019-11-21 14:04:53 +01:00
parent 0b6e3bf6f6
commit a5397b434c
15 changed files with 739 additions and 4 deletions

View File

@@ -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;

View File

@@ -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.">;

View File

@@ -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",

View File

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

View File

@@ -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");

View File

@@ -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);

View File

@@ -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;

View File

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

View File

@@ -2,6 +2,7 @@ silopt_register_sources(
CapturePromotion.cpp
CapturePropagation.cpp
ClosureSpecializer.cpp
CrossModuleSerializationSetup.cpp
DeadFunctionElimination.cpp
EagerSpecializer.cpp
GlobalOpt.cpp

View 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();
}

View File

@@ -411,6 +411,8 @@ static void addPerfEarlyModulePassPipeline(SILPassPipelinePlan &P) {
// Add the outliner pass (Osize).
P.addOutliner();
P.addCrossModuleSerializationSetup();
}
static void addHighLevelEarlyLoopOptPipeline(SILPassPipelinePlan &P) {

View File

@@ -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);
}

View 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)]
}

View 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)
}

View 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()