IR: Type ID summary extensions for WPD; thread summary into WPD pass.

Make the whole thing testable by adding YAML I/O support for the WPD
summary information and adding some negative tests that exercise the
YAML support.

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

llvm-svn: 294981
This commit is contained in:
Peter Collingbourne 2017-02-13 19:26:18 +00:00
parent a44ca284d3
commit 37b11338c0
11 changed files with 403 additions and 15 deletions

View File

@ -439,8 +439,40 @@ struct TypeTestResolution {
unsigned SizeM1BitWidth = 0;
};
struct WholeProgramDevirtResolution {
enum Kind {
Indir, ///< Just do a regular virtual call
SingleImpl, ///< Single implementation devirtualization
} TheKind = Indir;
std::string SingleImplName;
struct ByArg {
enum Kind {
Indir, ///< Just do a regular virtual call
UniformRetVal, ///< Uniform return value optimization
UniqueRetVal, ///< Unique return value optimization
VirtualConstProp, ///< Virtual constant propagation
} TheKind = Indir;
/// Additional information for the resolution:
/// - UniformRetVal: the uniform return value.
/// - UniqueRetVal: the return value associated with the unique vtable (0 or
/// 1).
uint64_t Info = 0;
};
/// Resolutions for calls with all constant integer arguments (excluding the
/// first argument, "this"), where the key is the argument vector.
std::map<std::vector<uint64_t>, ByArg> ResByArg;
};
struct TypeIdSummary {
TypeTestResolution TTRes;
/// Mapping from byte offset to whole-program devirt resolution for that
/// (typeid, byte offset) pair.
std::map<uint64_t, WholeProgramDevirtResolution> WPDRes;
};
/// 160 bits SHA1

View File

@ -33,14 +33,106 @@ template <> struct MappingTraits<TypeTestResolution> {
}
};
template <>
struct ScalarEnumerationTraits<WholeProgramDevirtResolution::ByArg::Kind> {
static void enumeration(IO &io,
WholeProgramDevirtResolution::ByArg::Kind &value) {
io.enumCase(value, "Indir", WholeProgramDevirtResolution::ByArg::Indir);
io.enumCase(value, "UniformRetVal",
WholeProgramDevirtResolution::ByArg::UniformRetVal);
io.enumCase(value, "UniqueRetVal",
WholeProgramDevirtResolution::ByArg::UniqueRetVal);
io.enumCase(value, "VirtualConstProp",
WholeProgramDevirtResolution::ByArg::VirtualConstProp);
}
};
template <> struct MappingTraits<WholeProgramDevirtResolution::ByArg> {
static void mapping(IO &io, WholeProgramDevirtResolution::ByArg &res) {
io.mapOptional("Kind", res.TheKind);
io.mapOptional("Info", res.Info);
}
};
template <>
struct CustomMappingTraits<
std::map<std::vector<uint64_t>, WholeProgramDevirtResolution::ByArg>> {
static void inputOne(
IO &io, StringRef Key,
std::map<std::vector<uint64_t>, WholeProgramDevirtResolution::ByArg> &V) {
std::vector<uint64_t> Args;
std::pair<StringRef, StringRef> P = {"", Key};
while (!P.second.empty()) {
P = P.second.split(',');
uint64_t Arg;
if (P.first.getAsInteger(0, Arg)) {
io.setError("key not an integer");
return;
}
Args.push_back(Arg);
}
io.mapRequired(Key.str().c_str(), V[Args]);
}
static void output(
IO &io,
std::map<std::vector<uint64_t>, WholeProgramDevirtResolution::ByArg> &V) {
for (auto &P : V) {
std::string Key;
for (uint64_t Arg : P.first) {
if (!Key.empty())
Key += ',';
Key += llvm::utostr(Arg);
}
io.mapRequired(Key.c_str(), P.second);
}
}
};
template <> struct ScalarEnumerationTraits<WholeProgramDevirtResolution::Kind> {
static void enumeration(IO &io, WholeProgramDevirtResolution::Kind &value) {
io.enumCase(value, "Indir", WholeProgramDevirtResolution::Indir);
io.enumCase(value, "SingleImpl", WholeProgramDevirtResolution::SingleImpl);
}
};
template <> struct MappingTraits<WholeProgramDevirtResolution> {
static void mapping(IO &io, WholeProgramDevirtResolution &res) {
io.mapOptional("Kind", res.TheKind);
io.mapOptional("SingleImplName", res.SingleImplName);
io.mapOptional("ResByArg", res.ResByArg);
}
};
template <>
struct CustomMappingTraits<std::map<uint64_t, WholeProgramDevirtResolution>> {
static void inputOne(IO &io, StringRef Key,
std::map<uint64_t, WholeProgramDevirtResolution> &V) {
uint64_t KeyInt;
if (Key.getAsInteger(0, KeyInt)) {
io.setError("key not an integer");
return;
}
io.mapRequired(Key.str().c_str(), V[KeyInt]);
}
static void output(IO &io, std::map<uint64_t, WholeProgramDevirtResolution> &V) {
for (auto &P : V)
io.mapRequired(llvm::utostr(P.first).c_str(), P.second);
}
};
template <> struct MappingTraits<TypeIdSummary> {
static void mapping(IO &io, TypeIdSummary& summary) {
io.mapOptional("TTRes", summary.TTRes);
io.mapOptional("WPDRes", summary.WPDRes);
}
};
struct FunctionSummaryYaml {
std::vector<uint64_t> TypeTests;
std::vector<FunctionSummary::VFuncId> TypeTestAssumeVCalls,
TypeCheckedLoadVCalls;
std::vector<FunctionSummary::ConstVCall> TypeTestAssumeConstVCalls,
TypeCheckedLoadConstVCalls;
};
} // End yaml namespace
@ -51,9 +143,38 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(uint64_t)
namespace llvm {
namespace yaml {
template <> struct MappingTraits<FunctionSummary::VFuncId> {
static void mapping(IO &io, FunctionSummary::VFuncId& id) {
io.mapOptional("GUID", id.GUID);
io.mapOptional("Offset", id.Offset);
}
};
template <> struct MappingTraits<FunctionSummary::ConstVCall> {
static void mapping(IO &io, FunctionSummary::ConstVCall& id) {
io.mapOptional("VFunc", id.VFunc);
io.mapOptional("Args", id.Args);
}
};
} // End yaml namespace
} // End llvm namespace
LLVM_YAML_IS_SEQUENCE_VECTOR(FunctionSummary::VFuncId)
LLVM_YAML_IS_SEQUENCE_VECTOR(FunctionSummary::ConstVCall)
namespace llvm {
namespace yaml {
template <> struct MappingTraits<FunctionSummaryYaml> {
static void mapping(IO &io, FunctionSummaryYaml& summary) {
io.mapOptional("TypeTests", summary.TypeTests);
io.mapOptional("TypeTestAssumeVCalls", summary.TypeTestAssumeVCalls);
io.mapOptional("TypeCheckedLoadVCalls", summary.TypeCheckedLoadVCalls);
io.mapOptional("TypeTestAssumeConstVCalls",
summary.TypeTestAssumeConstVCalls);
io.mapOptional("TypeCheckedLoadConstVCalls",
summary.TypeCheckedLoadConstVCalls);
}
};
@ -83,10 +204,10 @@ template <> struct CustomMappingTraits<GlobalValueSummaryMapTy> {
Elem.push_back(llvm::make_unique<FunctionSummary>(
GVFlags, 0, ArrayRef<ValueInfo>{},
ArrayRef<FunctionSummary::EdgeTy>{}, std::move(FSum.TypeTests),
ArrayRef<FunctionSummary::VFuncId>{},
ArrayRef<FunctionSummary::VFuncId>{},
ArrayRef<FunctionSummary::ConstVCall>{},
ArrayRef<FunctionSummary::ConstVCall>{}));
std::move(FSum.TypeTestAssumeVCalls),
std::move(FSum.TypeCheckedLoadVCalls),
std::move(FSum.TypeTestAssumeConstVCalls),
std::move(FSum.TypeCheckedLoadConstVCalls)));
}
}
static void output(IO &io, GlobalValueSummaryMapTy &V) {
@ -94,7 +215,11 @@ template <> struct CustomMappingTraits<GlobalValueSummaryMapTy> {
std::vector<FunctionSummaryYaml> FSums;
for (auto &Sum : P.second) {
if (auto *FSum = dyn_cast<FunctionSummary>(Sum.get()))
FSums.push_back(FunctionSummaryYaml{FSum->type_tests()});
FSums.push_back(FunctionSummaryYaml{
FSum->type_tests(), FSum->type_test_assume_vcalls(),
FSum->type_checked_load_vcalls(),
FSum->type_test_assume_const_vcalls(),
FSum->type_checked_load_const_vcalls()});
}
if (!FSums.empty())
io.mapRequired(llvm::utostr(P.first).c_str(), FSums);

View File

@ -235,7 +235,8 @@ ModulePass *createCrossDSOCFIPass();
/// \brief This pass implements whole-program devirtualization using type
/// metadata.
ModulePass *createWholeProgramDevirtPass();
ModulePass *createWholeProgramDevirtPass(PassSummaryAction Action,
ModuleSummaryIndex *Index);
/// This pass splits globals into pieces for the benefit of whole-program
/// devirtualization and control-flow integrity.

View File

@ -700,7 +700,8 @@ void PassManagerBuilder::addLTOOptimizationPasses(legacy::PassManagerBase &PM) {
PM.add(createGlobalSplitPass());
// Apply whole-program devirtualization and virtual constant propagation.
PM.add(createWholeProgramDevirtPass());
PM.add(createWholeProgramDevirtPass(
Summary ? PassSummaryAction::Export : PassSummaryAction::None, Summary));
// That's all we need at opt level 1.
if (OptLevel == 1)

View File

@ -54,10 +54,13 @@
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Metadata.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/ModuleSummaryIndexYAML.h"
#include "llvm/Pass.h"
#include "llvm/PassRegistry.h"
#include "llvm/PassSupport.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Transforms/IPO.h"
#include "llvm/Transforms/Utils/Evaluator.h"
@ -72,6 +75,26 @@ using namespace wholeprogramdevirt;
#define DEBUG_TYPE "wholeprogramdevirt"
static cl::opt<PassSummaryAction> ClSummaryAction(
"wholeprogramdevirt-summary-action",
cl::desc("What to do with the summary when running this pass"),
cl::values(clEnumValN(PassSummaryAction::None, "none", "Do nothing"),
clEnumValN(PassSummaryAction::Import, "import",
"Import typeid resolutions from summary and globals"),
clEnumValN(PassSummaryAction::Export, "export",
"Export typeid resolutions to summary and globals")),
cl::Hidden);
static cl::opt<std::string> ClReadSummary(
"wholeprogramdevirt-read-summary",
cl::desc("Read summary from given YAML file before running pass"),
cl::Hidden);
static cl::opt<std::string> ClWriteSummary(
"wholeprogramdevirt-write-summary",
cl::desc("Write summary to given YAML file after running pass"),
cl::Hidden);
// Find the minimum offset that we may store a value of size Size bits at. If
// IsAfter is set, look for an offset before the object, otherwise look for an
// offset after the object.
@ -261,6 +284,10 @@ struct VirtualCallSite {
struct DevirtModule {
Module &M;
PassSummaryAction Action;
ModuleSummaryIndex *Summary;
IntegerType *Int8Ty;
PointerType *Int8PtrTy;
IntegerType *Int32Ty;
@ -279,8 +306,9 @@ struct DevirtModule {
// true.
std::map<CallInst *, unsigned> NumUnsafeUsesForTypeTest;
DevirtModule(Module &M)
: M(M), Int8Ty(Type::getInt8Ty(M.getContext())),
DevirtModule(Module &M, PassSummaryAction Action, ModuleSummaryIndex *Summary)
: M(M), Action(Action), Summary(Summary),
Int8Ty(Type::getInt8Ty(M.getContext())),
Int8PtrTy(Type::getInt8PtrTy(M.getContext())),
Int32Ty(Type::getInt32Ty(M.getContext())),
RemarksEnabled(areRemarksEnabled()) {}
@ -315,20 +343,35 @@ struct DevirtModule {
void rebuildGlobal(VTableBits &B);
bool run();
// Lower the module using the action and summary passed as command line
// arguments. For testing purposes only.
static bool runForTesting(Module &M);
};
struct WholeProgramDevirt : public ModulePass {
static char ID;
WholeProgramDevirt() : ModulePass(ID) {
bool UseCommandLine = false;
PassSummaryAction Action;
ModuleSummaryIndex *Summary;
WholeProgramDevirt() : ModulePass(ID), UseCommandLine(true) {
initializeWholeProgramDevirtPass(*PassRegistry::getPassRegistry());
}
WholeProgramDevirt(PassSummaryAction Action, ModuleSummaryIndex *Summary)
: ModulePass(ID), Action(Action), Summary(Summary) {
initializeWholeProgramDevirtPass(*PassRegistry::getPassRegistry());
}
bool runOnModule(Module &M) override {
if (skipModule(M))
return false;
return DevirtModule(M).run();
if (UseCommandLine)
return DevirtModule::runForTesting(M);
return DevirtModule(M, Action, Summary).run();
}
};
@ -338,17 +381,50 @@ INITIALIZE_PASS(WholeProgramDevirt, "wholeprogramdevirt",
"Whole program devirtualization", false, false)
char WholeProgramDevirt::ID = 0;
ModulePass *llvm::createWholeProgramDevirtPass() {
return new WholeProgramDevirt;
ModulePass *llvm::createWholeProgramDevirtPass(PassSummaryAction Action,
ModuleSummaryIndex *Summary) {
return new WholeProgramDevirt(Action, Summary);
}
PreservedAnalyses WholeProgramDevirtPass::run(Module &M,
ModuleAnalysisManager &) {
if (!DevirtModule(M).run())
if (!DevirtModule(M, PassSummaryAction::None, nullptr).run())
return PreservedAnalyses::all();
return PreservedAnalyses::none();
}
bool DevirtModule::runForTesting(Module &M) {
ModuleSummaryIndex Summary;
// Handle the command-line summary arguments. This code is for testing
// purposes only, so we handle errors directly.
if (!ClReadSummary.empty()) {
ExitOnError ExitOnErr("-wholeprogramdevirt-read-summary: " + ClReadSummary +
": ");
auto ReadSummaryFile =
ExitOnErr(errorOrToExpected(MemoryBuffer::getFile(ClReadSummary)));
yaml::Input In(ReadSummaryFile->getBuffer());
In >> Summary;
ExitOnErr(errorCodeToError(In.error()));
}
bool Changed = DevirtModule(M, ClSummaryAction, &Summary).run();
if (!ClWriteSummary.empty()) {
ExitOnError ExitOnErr(
"-wholeprogramdevirt-write-summary: " + ClWriteSummary + ": ");
std::error_code EC;
raw_fd_ostream OS(ClWriteSummary, EC, sys::fs::F_Text);
ExitOnErr(errorCodeToError(EC));
yaml::Output Out(OS);
Out << Summary;
}
return Changed;
}
void DevirtModule::buildTypeIdentifierMap(
std::vector<VTableBits> &Bits,
DenseMap<Metadata *, std::set<TypeMemberInfo>> &TypeIdMap) {

View File

@ -153,7 +153,9 @@
; SUMMARY-NEXT: TTRes:
; SUMMARY-NEXT: Kind: AllOnes
; SUMMARY-NEXT: SizeM1BitWidth: 7
; SUMMARY-NEXT: WPDRes:
; SUMMARY-NEXT: typeid2:
; SUMMARY-NEXT: TTRes:
; SUMMARY-NEXT: Kind: AllOnes
; SUMMARY-NEXT: SizeM1BitWidth: 32
; SUMMARY-NEXT: WPDRes:

View File

@ -32,7 +32,9 @@
; SUMMARY-NEXT: TTRes:
; SUMMARY-NEXT: Kind: ByteArray
; SUMMARY-NEXT: SizeM1BitWidth: 7
; SUMMARY-NEXT: WPDRes:
; SUMMARY-NEXT: typeid2:
; SUMMARY-NEXT: TTRes:
; SUMMARY-NEXT: Kind: ByteArray
; SUMMARY-NEXT: SizeM1BitWidth: 32
; SUMMARY-NEXT: WPDRes:

View File

@ -27,7 +27,9 @@
; SUMMARY-NEXT: TTRes:
; SUMMARY-NEXT: Kind: Inline
; SUMMARY-NEXT: SizeM1BitWidth: 5
; SUMMARY-NEXT: WPDRes:
; SUMMARY-NEXT: typeid2:
; SUMMARY-NEXT: TTRes:
; SUMMARY-NEXT: Kind: Inline
; SUMMARY-NEXT: SizeM1BitWidth: 6
; SUMMARY-NEXT: WPDRes:

View File

@ -0,0 +1,41 @@
---
GlobalValueMap:
42:
- TypeTestAssumeVCalls:
- GUID: 123
Offset: 0
- GUID: 456
Offset: 4
TypeCheckedLoadVCalls:
- GUID: 789
Offset: 8
- GUID: 1234
Offset: 16
TypeTestAssumeConstVCalls:
- VFunc:
GUID: 123
Offset: 4
Args: [12, 24]
TypeCheckedLoadConstVCalls:
- VFunc:
GUID: 456
Offset: 8
Args: [24, 12]
TypeIdMap:
typeid1:
WPDRes:
0:
Kind: Indir
4:
Kind: Indir
ResByArg:
"":
Kind: UniformRetVal
Info: 12
12:
Kind: UniformRetVal
Info: 24
"12,24":
Kind: UniformRetVal
Info: 48
...

View File

@ -0,0 +1,7 @@
; RUN: opt -wholeprogramdevirt -wholeprogramdevirt-summary-action=export -wholeprogramdevirt-write-summary=%t -o /dev/null %s
; RUN: FileCheck %s < %t
; CHECK: ---
; CHECK-NEXT: GlobalValueMap:
; CHECK-NEXT: TypeIdMap:
; CHECK-NEXT: ...

View File

@ -0,0 +1,99 @@
; Test that we correctly import an indir resolution for type identifier "typeid1".
; RUN: opt -S -wholeprogramdevirt -wholeprogramdevirt-summary-action=import -wholeprogramdevirt-read-summary=%S/Inputs/import-indir.yaml -wholeprogramdevirt-write-summary=%t < %s | FileCheck %s
; RUN: FileCheck --check-prefix=SUMMARY %s < %t
; SUMMARY: GlobalValueMap:
; SUMMARY-NEXT: 42:
; SUMMARY-NEXT: - TypeTests:
; SUMMARY-NEXT: TypeTestAssumeVCalls:
; SUMMARY-NEXT: - GUID: 123
; SUMMARY-NEXT: Offset: 0
; SUMMARY-NEXT: - GUID: 456
; SUMMARY-NEXT: Offset: 4
; SUMMARY-NEXT: TypeCheckedLoadVCalls:
; SUMMARY-NEXT: - GUID: 789
; SUMMARY-NEXT: Offset: 8
; SUMMARY-NEXT: - GUID: 1234
; SUMMARY-NEXT: Offset: 16
; SUMMARY-NEXT: TypeTestAssumeConstVCalls:
; SUMMARY-NEXT: - VFunc:
; SUMMARY-NEXT: GUID: 123
; SUMMARY-NEXT: Offset: 4
; SUMMARY-NEXT: Args:
; SUMMARY-NEXT: - 12
; SUMMARY-NEXT: - 24
; SUMMARY-NEXT: TypeCheckedLoadConstVCalls:
; SUMMARY-NEXT: - VFunc:
; SUMMARY-NEXT: GUID: 456
; SUMMARY-NEXT: Offset: 8
; SUMMARY-NEXT: Args:
; SUMMARY-NEXT: - 24
; SUMMARY-NEXT: - 12
; SUMMARY-NEXT: TypeIdMap:
; SUMMARY-NEXT: typeid1:
; SUMMARY-NEXT: TTRes:
; SUMMARY-NEXT: Kind: Unsat
; SUMMARY-NEXT: SizeM1BitWidth: 0
; SUMMARY-NEXT: WPDRes:
; SUMMARY-NEXT: 0:
; SUMMARY-NEXT: Kind: Indir
; SUMMARY-NEXT: SingleImplName: ''
; SUMMARY-NEXT: ResByArg:
; SUMMARY-NEXT: 4:
; SUMMARY-NEXT: Kind: Indir
; SUMMARY-NEXT: SingleImplName: ''
; SUMMARY-NEXT: ResByArg:
; SUMMARY-NEXT: :
; SUMMARY-NEXT: Kind: UniformRetVal
; SUMMARY-NEXT: Info: 12
; SUMMARY-NEXT: 12:
; SUMMARY-NEXT: Kind: UniformRetVal
; SUMMARY-NEXT: Info: 24
; SUMMARY-NEXT: 12,24:
; SUMMARY-NEXT: Kind: UniformRetVal
; SUMMARY-NEXT: Info: 48
target datalayout = "e-p:32:32"
declare void @llvm.assume(i1)
declare void @llvm.trap()
declare {i8*, i1} @llvm.type.checked.load(i8*, i32, metadata)
declare i1 @llvm.type.test(i8*, metadata)
; CHECK: define i1 @f1
define i1 @f1(i8* %obj) {
%vtableptr = bitcast i8* %obj to [1 x i8*]**
%vtable = load [1 x i8*]*, [1 x i8*]** %vtableptr
%vtablei8 = bitcast [1 x i8*]* %vtable to i8*
%p = call i1 @llvm.type.test(i8* %vtablei8, metadata !"typeid1")
call void @llvm.assume(i1 %p)
%fptrptr = getelementptr [1 x i8*], [1 x i8*]* %vtable, i32 0, i32 0
%fptr = load i8*, i8** %fptrptr
%fptr_casted = bitcast i8* %fptr to i1 (i8*, i32)*
; CHECK: call i1 %
%result = call i1 %fptr_casted(i8* %obj, i32 5)
ret i1 %result
}
; CHECK: define i1 @f2
define i1 @f2(i8* %obj) {
%vtableptr = bitcast i8* %obj to [1 x i8*]**
%vtable = load [1 x i8*]*, [1 x i8*]** %vtableptr
%vtablei8 = bitcast [1 x i8*]* %vtable to i8*
%pair = call {i8*, i1} @llvm.type.checked.load(i8* %vtablei8, i32 4, metadata !"typeid1")
%fptr = extractvalue {i8*, i1} %pair, 0
%p = extractvalue {i8*, i1} %pair, 1
; CHECK: [[P:%.*]] = call i1 @llvm.type.test
; CHECK: br i1 [[P]]
br i1 %p, label %cont, label %trap
cont:
%fptr_casted = bitcast i8* %fptr to i1 (i8*, i32)*
; CHECK: call i1 %
%result = call i1 %fptr_casted(i8* %obj, i32 undef)
ret i1 %result
trap:
call void @llvm.trap()
unreachable
}