Skip to content

Improvements for cross-module-optimization #32495

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
Jun 23, 2020
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
6 changes: 6 additions & 0 deletions include/swift/SIL/SILGlobalVariable.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ class SILGlobalVariable
: public llvm::ilist_node<SILGlobalVariable>,
public SILAllocated<SILGlobalVariable>
{
public:
using const_iterator = SILBasicBlock::const_iterator;

private:
friend class SILModule;
friend class SILBuilder;
Expand Down Expand Up @@ -157,6 +160,9 @@ class SILGlobalVariable
return dyn_cast_or_null<ObjectInst>(getStaticInitializerValue()) != nullptr;
}

const_iterator begin() const { return StaticInitializerBlock.begin(); }
const_iterator end() const { return StaticInitializerBlock.end(); }

/// Returns true if \p I is a valid instruction to be contained in the
/// static initializer.
static bool isValidStaticInitializerInst(const SILInstruction *I,
Expand Down
4 changes: 3 additions & 1 deletion lib/SIL/IR/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2701,7 +2701,9 @@ void SILGlobalVariable::print(llvm::raw_ostream &OS, bool Verbose) const {
printClangQualifiedNameCommentIfPresent(OS, getClangDecl());

OS << "sil_global ";
printLinkage(OS, getLinkage(), isDefinition());
// Passing true for 'isDefinition' lets print the (external) linkage if it's
// not a definition.
printLinkage(OS, getLinkage(), /*isDefinition*/ true);

if (isSerialized())
OS << "[serialized] ";
Expand Down
99 changes: 73 additions & 26 deletions lib/SILOptimizer/IPO/CrossModuleSerializationSetup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "swift/SILOptimizer/Utils/SILInliner.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"

using namespace swift;

/// Functions up to this (abstract) size are serialized, even if they are not
/// generic.
static llvm::cl::opt<int> CMOFunctionSizeLimit("cmo-function-size-limit",
llvm::cl::init(20));

namespace {

/// Scans a whole module and marks functions and types as inlinable or usable
Expand All @@ -54,6 +60,8 @@ class CrossModuleSerializationSetup {

bool canSerialize(SILFunction *F, bool lookIntoThunks);

bool canSerialize(SILInstruction *inst, bool lookIntoThunks);

void setUpForSerialization(SILFunction *F);

void prepareInstructionForSerialization(SILInstruction *inst);
Expand Down Expand Up @@ -193,18 +201,31 @@ static llvm::cl::opt<bool> SerializeEverything(

/// 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() && !SerializeEverything)
return false;

// Check if we already handled this function before.
if (F->isSerialized() == IsSerialized)
return false;

if (F->hasSemanticsAttr("optimize.no.crossmodule"))
return false;

if (SerializeEverything)
return true;

// 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 true;

// Also serialize "small" non-generic functions.
int size = 0;
for (SILBasicBlock &block : *F) {
for (SILInstruction &inst : block) {
size += (int)instructionInlineCost(inst);
if (size >= CMOFunctionSizeLimit)
return false;
}
}

return true;
}

Expand All @@ -227,6 +248,11 @@ prepareInstructionForSerialization(SILInstruction *inst) {
handleReferencedFunction(callee);
return;
}
if (auto *GAI = dyn_cast<GlobalAddrInst>(inst)) {
GAI->getReferencedGlobal()->setSerialized(IsSerialized);
GAI->getReferencedGlobal()->setLinkage(SILLinkage::Public);
return;
}
if (auto *MI = dyn_cast<MethodInst>(inst)) {
handleReferencedMethod(MI->getMember());
return;
Expand Down Expand Up @@ -285,26 +311,42 @@ bool CrossModuleSerializationSetup::canSerialize(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, lookIntoThunks))
return false;
} else if (auto *KPI = dyn_cast<KeyPathInst>(&inst)) {
bool canUse = true;
KPI->getPattern()->visitReferencedFunctionsAndMethods(
[&](SILFunction *func) {
if (!canUseFromInline(func, lookIntoThunks))
canUse = false;
},
[](SILDeclRef method) { });
if (!canUse)
return false;
}
if (!canSerialize(&inst, lookIntoThunks))
return false;
}
}
return true;
}

bool CrossModuleSerializationSetup::canSerialize(SILInstruction *inst,
bool lookIntoThunks) {
if (auto *FRI = dyn_cast<FunctionRefBaseInst>(inst)) {
SILFunction *callee = FRI->getReferencedFunctionOrNull();
return canUseFromInline(callee, lookIntoThunks);
}
if (auto *KPI = dyn_cast<KeyPathInst>(inst)) {
bool canUse = true;
KPI->getPattern()->visitReferencedFunctionsAndMethods(
[&](SILFunction *func) {
if (!canUseFromInline(func, lookIntoThunks))
canUse = false;
},
[&](SILDeclRef method) {
if (method.isForeign)
canUse = false;
});
return canUse;
}
if (auto *GAI = dyn_cast<GlobalAddrInst>(inst)) {
return !GAI->getReferencedGlobal()->getName().startswith("globalinit_");
}
if (auto *MI = dyn_cast<MethodInst>(inst)) {
return !MI->getMember().isForeign;
}

return true;
}

/// Returns true if the function \p func can be used from a serialized function.
///
/// If \p lookIntoThunks is true, serializable shared thunks are also accepted.
Expand Down Expand Up @@ -351,12 +393,17 @@ void CrossModuleSerializationSetup::setUpForSerialization(SILFunction *F) {
}
F->setSerialized(IsSerialized);

// As a code size optimization, make serialized functions
// @alwaysEmitIntoClient.
// Also, for shared thunks it's required to make them @alwaysEmitIntoClient.
// SILLinkage::Public would not work for shared functions, because it could
// result in duplicate-symbol linker errors.
F->setLinkage(SILLinkage::PublicNonABI);
if (F->getLoweredFunctionType()->isPolymorphic() ||
F->getLinkage() != SILLinkage::Public) {
// As a code size optimization, make serialized functions
// @alwaysEmitIntoClient.
// Also, for shared thunks it's required to make them @alwaysEmitIntoClient.
// SILLinkage::Public would not work for shared functions, because it could
// result in duplicate-symbol linker errors.
F->setLinkage(SILLinkage::PublicNonABI);
} else {
F->setLinkage(SILLinkage::Public);
}
}

/// Select functions in the module which should be serialized.
Expand Down
4 changes: 4 additions & 0 deletions lib/SILOptimizer/PassManager/PassPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,10 @@ static void addPerfEarlyModulePassPipeline(SILPassPipelinePlan &P) {
// is linked in from the stdlib.
P.addTempRValueOpt();

// Needed to serialize static initializers of globals for cross-module
// optimization.
P.addGlobalOpt();

// Add the outliner pass (Osize).
P.addOutliner();

Expand Down
35 changes: 35 additions & 0 deletions lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
#include "swift/SILOptimizer/Utils/Devirtualize.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
Expand Down Expand Up @@ -807,6 +808,31 @@ static bool isZeroLoadFromEmptyCollection(LoadInst *LI) {
}
}

static SingleValueInstruction *getValueFromStaticLet(SILValue v) {
if (auto *globalAddr = dyn_cast<GlobalAddrInst>(v)) {
SILGlobalVariable *global = globalAddr->getReferencedGlobal();
if (!global->isLet())
return nullptr;
return dyn_cast_or_null<SingleValueInstruction>(
global->getStaticInitializerValue());
}
if (auto *seai = dyn_cast<StructElementAddrInst>(v)) {
auto *structVal = getValueFromStaticLet(seai->getOperand());
if (!structVal)
return nullptr;
return cast<SingleValueInstruction>(
cast<StructInst>(structVal)->getOperandForField(seai->getField())->get());
}
if (auto *teai = dyn_cast<TupleElementAddrInst>(v)) {
auto *tupleVal = getValueFromStaticLet(teai->getOperand());
if (!tupleVal)
return nullptr;
return cast<SingleValueInstruction>(
cast<TupleInst>(tupleVal)->getElement(teai->getFieldNo()));
}
return nullptr;
}

SILInstruction *SILCombiner::visitLoadInst(LoadInst *LI) {
if (LI->getFunction()->hasOwnership())
return nullptr;
Expand All @@ -833,6 +859,15 @@ SILInstruction *SILCombiner::visitLoadInst(LoadInst *LI) {
if (isZeroLoadFromEmptyCollection(LI))
return Builder.createIntegerLiteral(LI->getLoc(), LI->getType(), 0);

// Propagate a value from a static "let" global variable.
// This optimization is also done by GlobalOpt, but not with de-serialized
// globals, which can occur with cross-module optimization.
if (SingleValueInstruction *initVal = getValueFromStaticLet(LI->getOperand())) {
StaticInitCloner cloner(LI);
cloner.add(initVal);
return cloner.clone(initVal);
}

return nullptr;
}

Expand Down
85 changes: 75 additions & 10 deletions lib/Serialization/DeserializeSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -799,9 +799,10 @@ SILDeserializer::readSILFunctionChecked(DeclID FID, SILFunction *existingFn,
// occurred and this is a declaration. Work around that for now.
if (!CurrentBB)
return fn;
Builder.setInsertionPoint(CurrentBB);

// Handle a SILInstruction record.
if (readSILInstruction(fn, CurrentBB, Builder, kind, scratch)) {
if (readSILInstruction(fn, Builder, kind, scratch)) {
LLVM_DEBUG(llvm::dbgs() << "readSILInstruction returns error.\n");
MF->fatal();
}
Expand Down Expand Up @@ -1031,16 +1032,12 @@ SILDeserializer::readKeyPathComponent(ArrayRef<uint64_t> ListOfValues,
llvm_unreachable("invalid key path component kind encoding");
}

bool SILDeserializer::readSILInstruction(SILFunction *Fn, SILBasicBlock *BB,
bool SILDeserializer::readSILInstruction(SILFunction *Fn,
SILBuilder &Builder,
unsigned RecordKind,
SmallVectorImpl<uint64_t> &scratch) {
// Return error if Basic Block is null.
if (!BB)
return true;

Builder.setInsertionPoint(BB);
Builder.setCurrentDebugScope(Fn->getDebugScope());
if (Fn)
Builder.setCurrentDebugScope(Fn->getDebugScope());
unsigned RawOpCode = 0, TyCategory = 0, TyCategory2 = 0, TyCategory3 = 0,
Attr = 0, Attr2 = 0, Attr3 = 0, Attr4 = 0, NumSubs = 0,
NumConformances = 0, IsNonThrowingApply = 0;
Expand Down Expand Up @@ -2096,7 +2093,22 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn, SILBasicBlock *BB,
break;
}
case SILInstructionKind::ObjectInst: {
llvm_unreachable("Serialization of global initializers not supported");
assert(RecordKind == SIL_ONE_TYPE_VALUES &&
"Layout should be OneTypeValues.");
unsigned NumVals = ListOfValues.size();
assert(NumVals >= 1 && "Not enough values");
unsigned numBaseElements = ListOfValues[0];
SILType ClassTy =
getSILType(MF->getType(TyID), (SILValueCategory)TyCategory, Fn);
SmallVector<SILValue, 4> elements;
for (unsigned i = 1; i < NumVals; i += 2) {
SILType elementType = getSILType(MF->getType(ListOfValues[i + 1]),
SILValueCategory::Object, Fn);
SILValue elementVal = getLocalValue(ListOfValues[i], elementType);
elements.push_back(elementVal);
}
ResultVal = Builder.createObject(Loc, ClassTy, elements, numBaseElements);
break;
}
case SILInstructionKind::BranchInst: {
SmallVector<SILValue, 4> Args;
Expand Down Expand Up @@ -2927,7 +2939,60 @@ SILGlobalVariable *SILDeserializer::readGlobalVar(StringRef Name) {
globalVarOrOffset = v;
v->setDeclaration(IsDeclaration);

if (Callback) Callback->didDeserialize(MF->getAssociatedModule(), v);
if (Callback)
Callback->didDeserialize(MF->getAssociatedModule(), v);

scratch.clear();
maybeEntry = SILCursor.advance(AF_DontPopBlockAtEnd);
if (!maybeEntry)
MF->fatal(maybeEntry.takeError());
entry = maybeEntry.get();
if (entry.Kind == llvm::BitstreamEntry::EndBlock)
return v;

maybeKind = SILCursor.readRecord(entry.ID, scratch, &blobData);
if (!maybeKind)
MF->fatal(maybeKind.takeError());
kind = maybeKind.get();

SILBuilder Builder(v);

llvm::DenseMap<uint32_t, ValueBase*> SavedLocalValues;
llvm::DenseMap<uint32_t, ValueBase*> SavedForwardLocalValues;
serialization::ValueID SavedLastValueID = 1;

SavedLocalValues.swap(LocalValues);
SavedForwardLocalValues.swap(ForwardLocalValues);
std::swap(SavedLastValueID, LastValueID);

while (kind != SIL_FUNCTION && kind != SIL_VTABLE && kind != SIL_GLOBALVAR &&
kind != SIL_WITNESS_TABLE && kind != SIL_DIFFERENTIABILITY_WITNESS) {
if (readSILInstruction(nullptr, Builder, kind, scratch)) {
LLVM_DEBUG(llvm::dbgs() << "readSILInstruction returns error.\n");
MF->fatal();
}

// Fetch the next record.
scratch.clear();
llvm::Expected<llvm::BitstreamEntry> maybeEntry =
SILCursor.advance(AF_DontPopBlockAtEnd);
if (!maybeEntry)
MF->fatal(maybeEntry.takeError());
llvm::BitstreamEntry entry = maybeEntry.get();

// EndBlock means the end of this SILFunction.
if (entry.Kind == llvm::BitstreamEntry::EndBlock)
break;
maybeKind = SILCursor.readRecord(entry.ID, scratch);
if (!maybeKind)
MF->fatal(maybeKind.takeError());
kind = maybeKind.get();
}

SavedLocalValues.swap(LocalValues);
SavedForwardLocalValues.swap(ForwardLocalValues);
std::swap(SavedLastValueID, LastValueID);

return v;
}

Expand Down
4 changes: 2 additions & 2 deletions lib/Serialization/DeserializeSIL.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ namespace swift {
SILBasicBlock *readSILBasicBlock(SILFunction *Fn,
SILBasicBlock *Prev,
SmallVectorImpl<uint64_t> &scratch);
/// Read a SIL instruction within a given SIL basic block.
bool readSILInstruction(SILFunction *Fn, SILBasicBlock *BB,
/// Read a SIL instruction.
bool readSILInstruction(SILFunction *Fn,
SILBuilder &Builder,
unsigned RecordKind,
SmallVectorImpl<uint64_t> &scratch);
Expand Down
2 changes: 1 addition & 1 deletion lib/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 560; // SILVTable flag for non-overridden entries
const uint16_t SWIFTMODULE_VERSION_MINOR = 561; // Initializers of globals.

/// A standard hash seed used for all string hashes in a serialized module.
///
Expand Down
Loading