IPO: Introduce ThinLTOBitcodeWriter pass.

This pass prepares a module containing type metadata for ThinLTO by splitting
it into regular and thin LTO parts if possible, and writing both parts to
a multi-module bitcode file. Modules that do not contain type metadata are
written unmodified as a single module.

All globals with type metadata are added to the regular LTO module, and
the rest are added to the thin LTO module.

Differential Revision: https://reviews.llvm.org/D27324

llvm-svn: 289899
This commit is contained in:
Peter Collingbourne 2016-12-16 00:26:30 +00:00
parent 1b48bac330
commit 1398a32e28
11 changed files with 516 additions and 1 deletions

View File

@ -354,6 +354,7 @@ void initializeVirtRegRewriterPass(PassRegistry&);
void initializeWholeProgramDevirtPass(PassRegistry &);
void initializeWinEHPreparePass(PassRegistry&);
void initializeWriteBitcodePassPass(PassRegistry &);
void initializeWriteThinLTOBitcodePass(PassRegistry &);
void initializeXRayInstrumentationPass(PassRegistry &);
}

View File

@ -28,6 +28,7 @@ class Pass;
class Function;
class BasicBlock;
class GlobalValue;
class raw_ostream;
//===----------------------------------------------------------------------===//
//
@ -235,6 +236,9 @@ ModulePass *createGlobalSplitPass();
ModulePass *createSampleProfileLoaderPass();
ModulePass *createSampleProfileLoaderPass(StringRef Name);
/// Write ThinLTO-ready bitcode to Str.
ModulePass *createWriteThinLTOBitcodePass(raw_ostream &Str);
} // End llvm namespace
#endif

View File

@ -28,6 +28,7 @@ add_llvm_library(LLVMipo
SampleProfile.cpp
StripDeadPrototypes.cpp
StripSymbols.cpp
ThinLTOBitcodeWriter.cpp
WholeProgramDevirt.cpp
ADDITIONAL_HEADER_DIRS

View File

@ -0,0 +1,344 @@
//===- ThinLTOBitcodeWriter.cpp - Bitcode writing pass for ThinLTO --------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This pass prepares a module containing type metadata for ThinLTO by splitting
// it into regular and thin LTO parts if possible, and writing both parts to
// a multi-module bitcode file. Modules that do not contain type metadata are
// written unmodified as a single module.
//
//===----------------------------------------------------------------------===//
#include "llvm/Transforms/IPO.h"
#include "llvm/Analysis/ModuleSummaryAnalysis.h"
#include "llvm/Analysis/TypeMetadataUtils.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/PassManager.h"
#include "llvm/Pass.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Transforms/Utils/Cloning.h"
using namespace llvm;
namespace {
// Produce a unique identifier for this module by taking the MD5 sum of the
// names of the module's strong external symbols. This identifier is
// normally guaranteed to be unique, or the program would fail to link due to
// multiply defined symbols.
//
// If the module has no strong external symbols (such a module may still have a
// semantic effect if it performs global initialization), we cannot produce a
// unique identifier for this module, so we return the empty string, which
// causes the entire module to be written as a regular LTO module.
std::string getModuleId(Module *M) {
MD5 Md5;
bool ExportsSymbols = false;
auto AddGlobal = [&](GlobalValue &GV) {
if (GV.isDeclaration() || GV.getName().startswith("llvm.") ||
!GV.hasExternalLinkage())
return;
ExportsSymbols = true;
Md5.update(GV.getName());
Md5.update(ArrayRef<uint8_t>{0});
};
for (auto &F : *M)
AddGlobal(F);
for (auto &GV : M->globals())
AddGlobal(GV);
for (auto &GA : M->aliases())
AddGlobal(GA);
for (auto &IF : M->ifuncs())
AddGlobal(IF);
if (!ExportsSymbols)
return "";
MD5::MD5Result R;
Md5.final(R);
SmallString<32> Str;
MD5::stringifyResult(R, Str);
return ("$" + Str).str();
}
// Promote each local-linkage entity defined by ExportM and used by ImportM by
// changing visibility and appending the given ModuleId.
void promoteInternals(Module &ExportM, Module &ImportM, StringRef ModuleId) {
auto PromoteInternal = [&](GlobalValue &ExportGV) {
if (!ExportGV.hasLocalLinkage())
return;
GlobalValue *ImportGV = ImportM.getNamedValue(ExportGV.getName());
if (!ImportGV || ImportGV->use_empty())
return;
std::string NewName = (ExportGV.getName() + ModuleId).str();
ExportGV.setName(NewName);
ExportGV.setLinkage(GlobalValue::ExternalLinkage);
ExportGV.setVisibility(GlobalValue::HiddenVisibility);
ImportGV->setName(NewName);
ImportGV->setVisibility(GlobalValue::HiddenVisibility);
};
for (auto &F : ExportM)
PromoteInternal(F);
for (auto &GV : ExportM.globals())
PromoteInternal(GV);
for (auto &GA : ExportM.aliases())
PromoteInternal(GA);
for (auto &IF : ExportM.ifuncs())
PromoteInternal(IF);
}
// Promote all internal (i.e. distinct) type ids used by the module by replacing
// them with external type ids formed using the module id.
//
// Note that this needs to be done before we clone the module because each clone
// will receive its own set of distinct metadata nodes.
void promoteTypeIds(Module &M, StringRef ModuleId) {
DenseMap<Metadata *, Metadata *> LocalToGlobal;
auto ExternalizeTypeId = [&](CallInst *CI, unsigned ArgNo) {
Metadata *MD =
cast<MetadataAsValue>(CI->getArgOperand(ArgNo))->getMetadata();
if (isa<MDNode>(MD) && cast<MDNode>(MD)->isDistinct()) {
Metadata *&GlobalMD = LocalToGlobal[MD];
if (!GlobalMD) {
std::string NewName =
(to_string(LocalToGlobal.size()) + ModuleId).str();
GlobalMD = MDString::get(M.getContext(), NewName);
}
CI->setArgOperand(ArgNo,
MetadataAsValue::get(M.getContext(), GlobalMD));
}
};
if (Function *TypeTestFunc =
M.getFunction(Intrinsic::getName(Intrinsic::type_test))) {
for (const Use &U : TypeTestFunc->uses()) {
auto CI = cast<CallInst>(U.getUser());
ExternalizeTypeId(CI, 1);
}
}
if (Function *TypeCheckedLoadFunc =
M.getFunction(Intrinsic::getName(Intrinsic::type_checked_load))) {
for (const Use &U : TypeCheckedLoadFunc->uses()) {
auto CI = cast<CallInst>(U.getUser());
ExternalizeTypeId(CI, 2);
}
}
for (GlobalObject &GO : M.global_objects()) {
SmallVector<MDNode *, 1> MDs;
GO.getMetadata(LLVMContext::MD_type, MDs);
GO.eraseMetadata(LLVMContext::MD_type);
for (auto MD : MDs) {
auto I = LocalToGlobal.find(MD->getOperand(1));
if (I == LocalToGlobal.end()) {
GO.addMetadata(LLVMContext::MD_type, *MD);
continue;
}
GO.addMetadata(
LLVMContext::MD_type,
*MDNode::get(M.getContext(),
ArrayRef<Metadata *>{MD->getOperand(0), I->second}));
}
}
}
// Drop unused globals, and drop type information from function declarations.
// FIXME: If we made functions typeless then there would be no need to do this.
void simplifyExternals(Module &M) {
FunctionType *EmptyFT =
FunctionType::get(Type::getVoidTy(M.getContext()), false);
for (auto I = M.begin(), E = M.end(); I != E;) {
Function &F = *I++;
if (F.isDeclaration() && F.use_empty()) {
F.eraseFromParent();
continue;
}
if (!F.isDeclaration() || F.getFunctionType() == EmptyFT)
continue;
Function *NewF =
Function::Create(EmptyFT, GlobalValue::ExternalLinkage, "", &M);
NewF->setVisibility(F.getVisibility());
NewF->takeName(&F);
F.replaceAllUsesWith(ConstantExpr::getBitCast(NewF, F.getType()));
F.eraseFromParent();
}
for (auto I = M.global_begin(), E = M.global_end(); I != E;) {
GlobalVariable &GV = *I++;
if (GV.isDeclaration() && GV.use_empty()) {
GV.eraseFromParent();
continue;
}
}
}
void filterModule(
Module *M, std::function<bool(const GlobalValue *)> ShouldKeepDefinition) {
for (Function &F : *M) {
if (ShouldKeepDefinition(&F))
continue;
F.deleteBody();
F.clearMetadata();
}
for (GlobalVariable &GV : M->globals()) {
if (ShouldKeepDefinition(&GV))
continue;
GV.setInitializer(nullptr);
GV.setLinkage(GlobalValue::ExternalLinkage);
GV.clearMetadata();
}
for (Module::alias_iterator I = M->alias_begin(), E = M->alias_end();
I != E;) {
GlobalAlias *GA = &*I++;
if (ShouldKeepDefinition(GA))
continue;
GlobalObject *GO;
if (I->getValueType()->isFunctionTy())
GO = Function::Create(cast<FunctionType>(GA->getValueType()),
GlobalValue::ExternalLinkage, "", M);
else
GO = new GlobalVariable(
*M, GA->getValueType(), false, GlobalValue::ExternalLinkage,
(Constant *)nullptr, "", (GlobalVariable *)nullptr,
GA->getThreadLocalMode(), GA->getType()->getAddressSpace());
GO->takeName(GA);
GA->replaceAllUsesWith(GO);
GA->eraseFromParent();
}
}
// If it's possible to split M into regular and thin LTO parts, do so and write
// a multi-module bitcode file with the two parts to OS. Otherwise, write only a
// regular LTO bitcode file to OS.
void splitAndWriteThinLTOBitcode(raw_ostream &OS, Module &M) {
std::string ModuleId = getModuleId(&M);
if (ModuleId.empty()) {
// We couldn't generate a module ID for this module, just write it out as a
// regular LTO module.
WriteBitcodeToFile(&M, OS);
return;
}
promoteTypeIds(M, ModuleId);
auto IsInMergedM = [&](const GlobalValue *GV) {
auto *GVar = dyn_cast<GlobalVariable>(GV->getBaseObject());
if (!GVar)
return false;
SmallVector<MDNode *, 1> MDs;
GVar->getMetadata(LLVMContext::MD_type, MDs);
return !MDs.empty();
};
ValueToValueMapTy VMap;
std::unique_ptr<Module> MergedM(CloneModule(&M, VMap, IsInMergedM));
filterModule(&M, [&](const GlobalValue *GV) { return !IsInMergedM(GV); });
promoteInternals(*MergedM, M, ModuleId);
promoteInternals(M, *MergedM, ModuleId);
simplifyExternals(*MergedM);
SmallVector<char, 0> Buffer;
BitcodeWriter W(Buffer);
// FIXME: Try to re-use BSI and PFI from the original module here.
ModuleSummaryIndex Index = buildModuleSummaryIndex(M, nullptr, nullptr);
W.writeModule(&M, /*ShouldPreserveUseListOrder=*/false, &Index,
/*GenerateHash=*/true);
W.writeModule(MergedM.get());
OS << Buffer;
}
// Returns whether this module needs to be split because it uses type metadata.
bool requiresSplit(Module &M) {
SmallVector<MDNode *, 1> MDs;
for (auto &GO : M.global_objects()) {
GO.getMetadata(LLVMContext::MD_type, MDs);
if (!MDs.empty())
return true;
}
return false;
}
void writeThinLTOBitcode(raw_ostream &OS, Module &M,
const ModuleSummaryIndex *Index) {
// See if this module has any type metadata. If so, we need to split it.
if (requiresSplit(M))
return splitAndWriteThinLTOBitcode(OS, M);
// Otherwise we can just write it out as a regular module.
WriteBitcodeToFile(&M, OS, /*ShouldPreserveUseListOrder=*/false, Index,
/*GenerateHash=*/true);
}
class WriteThinLTOBitcode : public ModulePass {
raw_ostream &OS; // raw_ostream to print on
public:
static char ID; // Pass identification, replacement for typeid
WriteThinLTOBitcode() : ModulePass(ID), OS(dbgs()) {
initializeWriteThinLTOBitcodePass(*PassRegistry::getPassRegistry());
}
explicit WriteThinLTOBitcode(raw_ostream &o)
: ModulePass(ID), OS(o) {
initializeWriteThinLTOBitcodePass(*PassRegistry::getPassRegistry());
}
StringRef getPassName() const override { return "ThinLTO Bitcode Writer"; }
bool runOnModule(Module &M) override {
const ModuleSummaryIndex *Index =
&(getAnalysis<ModuleSummaryIndexWrapperPass>().getIndex());
writeThinLTOBitcode(OS, M, Index);
return true;
}
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.setPreservesAll();
AU.addRequired<ModuleSummaryIndexWrapperPass>();
}
};
} // anonymous namespace
char WriteThinLTOBitcode::ID = 0;
INITIALIZE_PASS_BEGIN(WriteThinLTOBitcode, "write-thinlto-bitcode",
"Write ThinLTO Bitcode", false, true)
INITIALIZE_PASS_DEPENDENCY(ModuleSummaryIndexWrapperPass)
INITIALIZE_PASS_END(WriteThinLTOBitcode, "write-thinlto-bitcode",
"Write ThinLTO Bitcode", false, true)
ModulePass *llvm::createWriteThinLTOBitcodePass(raw_ostream &Str) {
return new WriteThinLTOBitcode(Str);
}

View File

@ -0,0 +1,13 @@
; RUN: opt -thinlto-bc -o %t %s
; RUN: llvm-dis -o - %t | FileCheck %s
; RUN: llvm-bcanalyzer -dump %t | FileCheck --check-prefix=BCA %s
; BCA: <GLOBALVAL_SUMMARY_BLOCK
; CHECK: @g = global i8 42
@g = global i8 42
; CHECK: define void @f()
define void @f() {
ret void
}

View File

@ -0,0 +1,40 @@
; RUN: opt -thinlto-bc -o %t %s
; RUN: llvm-modextract -b -n 0 -o %t0 %t
; RUN: llvm-modextract -b -n 1 -o %t1 %t
; RUN: not llvm-modextract -b -n 2 -o - %t 2>&1 | FileCheck --check-prefix=ERROR %s
; RUN: llvm-dis -o - %t0 | FileCheck --check-prefix=M0 %s
; RUN: llvm-dis -o - %t1 | FileCheck --check-prefix=M1 %s
; RUN: llvm-bcanalyzer -dump %t0 | FileCheck --check-prefix=BCA0 %s
; RUN: llvm-bcanalyzer -dump %t1 | FileCheck --check-prefix=BCA1 %s
; ERROR: llvm-modextract: error: module index out of range; bitcode file contains 2 module(s)
; BCA0: <GLOBALVAL_SUMMARY_BLOCK
; BCA1-NOT: <GLOBALVAL_SUMMARY_BLOCK
; M0: @g = external global i8{{$}}
; M1: @g = global i8 42, !type !0, !type !1, !type !2
@g = global i8 42, !type !1, !type !2, !type !4
; M0: define void @f()
; M1-NOT: @f()
define void @f() {
; M0: llvm.type.test{{.*}}metadata !"1$f50b51a12bb012bebbeff978335e34cf"
%p = call i1 @llvm.type.test(i8* null, metadata !0)
; M0: llvm.type.checked.load{{.*}}metadata !"2$f50b51a12bb012bebbeff978335e34cf"
%q = call {i8*, i1} @llvm.type.checked.load(i8* null, i32 0, metadata !3)
ret void
}
declare i1 @llvm.type.test(i8*, metadata)
declare {i8*, i1} @llvm.type.checked.load(i8*, i32, metadata)
!0 = distinct !{}
; M1: !0 = !{i32 0, !"1$f50b51a12bb012bebbeff978335e34cf"}
!1 = !{i32 0, !0}
; M1: !1 = !{i32 1, !"1$f50b51a12bb012bebbeff978335e34cf"}
!2 = !{i32 1, !0}
!3 = distinct !{}
; M1: !2 = !{i32 0, !"2$f50b51a12bb012bebbeff978335e34cf"}
!4 = !{i32 0, !3}

View File

@ -0,0 +1,27 @@
; RUN: opt -thinlto-bc -o %t %s
; RUN: llvm-modextract -b -n 0 -o %t0 %t
; RUN: llvm-modextract -b -n 1 -o %t1 %t
; RUN: not llvm-modextract -b -n 2 -o - %t 2>&1 | FileCheck --check-prefix=ERROR %s
; RUN: llvm-dis -o - %t0 | FileCheck --check-prefix=M0 %s
; RUN: llvm-dis -o - %t1 | FileCheck --check-prefix=M1 %s
; RUN: llvm-bcanalyzer -dump %t0 | FileCheck --check-prefix=BCA0 %s
; RUN: llvm-bcanalyzer -dump %t1 | FileCheck --check-prefix=BCA1 %s
; ERROR: llvm-modextract: error: module index out of range; bitcode file contains 2 module(s)
; BCA0: <GLOBALVAL_SUMMARY_BLOCK
; BCA1-NOT: <GLOBALVAL_SUMMARY_BLOCK
; M0: @"g$581d7631532fa146ba4061179da39272" = external hidden global i8{{$}}
; M1: @"g$581d7631532fa146ba4061179da39272" = hidden global i8 42, !type !0
@g = internal global i8 42, !type !0
; M0: define i8* @f()
; M1-NOT: @f()
define i8* @f() {
; M0: ret i8* @"g$581d7631532fa146ba4061179da39272"
ret i8* @g
}
; M1: !0 = !{i32 0, !"typeid"}
!0 = !{i32 0, !"typeid"}

View File

@ -0,0 +1,32 @@
; RUN: opt -thinlto-bc -o %t %s
; RUN: llvm-modextract -b -n 0 -o %t0 %t
; RUN: llvm-modextract -b -n 1 -o %t1 %t
; RUN: not llvm-modextract -b -n 2 -o - %t 2>&1 | FileCheck --check-prefix=ERROR %s
; RUN: llvm-dis -o - %t0 | FileCheck --check-prefix=M0 %s
; RUN: llvm-dis -o - %t1 | FileCheck --check-prefix=M1 %s
; RUN: llvm-bcanalyzer -dump %t0 | FileCheck --check-prefix=BCA0 %s
; RUN: llvm-bcanalyzer -dump %t1 | FileCheck --check-prefix=BCA1 %s
; ERROR: llvm-modextract: error: module index out of range; bitcode file contains 2 module(s)
; BCA0: <GLOBALVAL_SUMMARY_BLOCK
; BCA1-NOT: <GLOBALVAL_SUMMARY_BLOCK
; M0: @g = external global void ()*{{$}}
; M1: @g = global void ()* @"f$13757e0fb71915e385efa4dc9d1e08fd", !type !0
@g = global void ()* @f, !type !0
; M0: define hidden void @"f$13757e0fb71915e385efa4dc9d1e08fd"()
; M1: declare hidden void @"f$13757e0fb71915e385efa4dc9d1e08fd"()
define internal void @f() {
call void @f2()
ret void
}
; M0: define internal void @f2()
define internal void @f2() {
ret void
}
; M1: !0 = !{i32 0, !"typeid"}
!0 = !{i32 0, !"typeid"}

View File

@ -0,0 +1,26 @@
; RUN: opt -thinlto-bc -o %t %s
; RUN: llvm-modextract -b -n 0 -o %t0 %t
; RUN: llvm-modextract -b -n 1 -o %t1 %t
; RUN: not llvm-modextract -b -n 2 -o - %t 2>&1 | FileCheck --check-prefix=ERROR %s
; RUN: llvm-dis -o - %t0 | FileCheck --check-prefix=M0 %s
; RUN: llvm-dis -o - %t1 | FileCheck --check-prefix=M1 %s
; RUN: llvm-bcanalyzer -dump %t0 | FileCheck --check-prefix=BCA0 %s
; RUN: llvm-bcanalyzer -dump %t1 | FileCheck --check-prefix=BCA1 %s
; ERROR: llvm-modextract: error: module index out of range; bitcode file contains 2 module(s)
; BCA0: <GLOBALVAL_SUMMARY_BLOCK
; BCA1-NOT: <GLOBALVAL_SUMMARY_BLOCK
; M0: @g = external global i8{{$}}
; M1: @g = global i8 42, !type !0
@g = global i8 42, !type !0
; M0: define i8* @f()
; M1-NOT: @f()
define i8* @f() {
ret i8* @g
}
; M1: !0 = !{i32 0, !"typeid"}
!0 = !{i32 0, !"typeid"}

View File

@ -0,0 +1,21 @@
; RUN: opt -thinlto-bc -o %t %s
; RUN: llvm-dis -o - %t | FileCheck %s
; RUN: llvm-bcanalyzer -dump %t | FileCheck --check-prefix=BCA %s
; BCA-NOT: <GLOBALVAL_SUMMARY_BLOCK
; CHECK: @llvm.global_ctors = appending global
@llvm.global_ctors = appending global [1 x { i32, void ()* }] [{ i32, void ()* } { i32 65535, void ()* @f }]
; CHECK: @g = internal global i8 42, !type !0
@g = internal global i8 42, !type !0
declare void @sink(i8*)
; CHECK: define internal void @f()
define internal void @f() {
call void @sink(i8* @g)
ret void
}
!0 = !{i32 0, !"typeid"}

View File

@ -98,6 +98,10 @@ NoOutput("disable-output",
static cl::opt<bool>
OutputAssembly("S", cl::desc("Write output as LLVM assembly"));
static cl::opt<bool>
OutputThinLTOBC("thinlto-bc",
cl::desc("Write output as ThinLTO-ready bitcode"));
static cl::opt<bool>
NoVerify("disable-verify", cl::desc("Do not run the verifier"), cl::Hidden);
@ -704,7 +708,9 @@ int main(int argc, char **argv) {
if (EmitModuleHash)
report_fatal_error("Text output is incompatible with -module-hash");
Passes.add(createPrintModulePass(*OS, "", PreserveAssemblyUseListOrder));
} else
} else if (OutputThinLTOBC)
Passes.add(createWriteThinLTOBitcodePass(*OS));
else
Passes.add(createBitcodeWriterPass(*OS, PreserveBitcodeUseListOrder,
EmitSummaryIndex, EmitModuleHash));
}