Skip to content

Commit d862c17

Browse files
committed
[esan] EfficiencySanitizer instrumentation pass
Summary: Adds an instrumentation pass for the new EfficiencySanitizer ("esan") performance tuning family of tools. Multiple tools will be supported within the same framework. Preliminary support for a cache fragmentation tool is included here. The shared instrumentation includes: + Turn mem{set,cpy,move} instrinsics into library calls. + Slowpath instrumentation of loads and stores via callouts to the runtime library. + Fastpath instrumentation will be per-tool. + Which memory accesses to ignore will be per-tool. Reviewers: eugenis, vitalybuka, aizatsky, filcab Subscribers: filcab, vkalintiris, pcc, silvas, llvm-commits, zhaoqin, kcc Differential Revision: http://reviews.llvm.org/D19167 llvm-svn: 267058
1 parent 1a0e097 commit d862c17

File tree

6 files changed

+625
-0
lines changed

6 files changed

+625
-0
lines changed

llvm/include/llvm/InitializePasses.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ void initializeMemorySanitizerPass(PassRegistry&);
131131
void initializeThreadSanitizerPass(PassRegistry&);
132132
void initializeSanitizerCoverageModulePass(PassRegistry&);
133133
void initializeDataFlowSanitizerPass(PassRegistry&);
134+
void initializeEfficiencySanitizerPass(PassRegistry&);
134135
void initializeScalarizerPass(PassRegistry&);
135136
void initializeEarlyCSELegacyPassPass(PassRegistry &);
136137
void initializeEliminateAvailableExternallyPass(PassRegistry&);

llvm/include/llvm/Transforms/Instrumentation.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,19 @@ ModulePass *createDataFlowSanitizerPass(
116116
const std::vector<std::string> &ABIListFiles = std::vector<std::string>(),
117117
void *(*getArgTLS)() = nullptr, void *(*getRetValTLS)() = nullptr);
118118

119+
// Options for EfficiencySanitizer sub-tools.
120+
struct EfficiencySanitizerOptions {
121+
EfficiencySanitizerOptions() : ToolType(ESAN_None) {}
122+
enum Type {
123+
ESAN_None = 0,
124+
ESAN_CacheFrag,
125+
} ToolType;
126+
};
127+
128+
// Insert EfficiencySanitizer instrumentation.
129+
FunctionPass *createEfficiencySanitizerPass(
130+
const EfficiencySanitizerOptions &Options = EfficiencySanitizerOptions());
131+
119132
// Options for sanitizer coverage instrumentation.
120133
struct SanitizerCoverageOptions {
121134
SanitizerCoverageOptions()

llvm/lib/Transforms/Instrumentation/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ add_llvm_library(LLVMInstrumentation
99
PGOInstrumentation.cpp
1010
SanitizerCoverage.cpp
1111
ThreadSanitizer.cpp
12+
EfficiencySanitizer.cpp
1213

1314
ADDITIONAL_HEADER_DIRS
1415
${LLVM_MAIN_INCLUDE_DIR}/llvm/Transforms
Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
//===-- EfficiencySanitizer.cpp - performance tuner -----------------------===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This file is a part of EfficiencySanitizer, a family of performance tuners
11+
// that detects multiple performance issues via separate sub-tools.
12+
//
13+
// The instrumentation phase is straightforward:
14+
// - Take action on every memory access: either inlined instrumentation,
15+
// or Inserted calls to our run-time library.
16+
// - Optimizations may apply to avoid instrumenting some of the accesses.
17+
// - Turn mem{set,cpy,move} instrinsics into library calls.
18+
// The rest is handled by the run-time library.
19+
//===----------------------------------------------------------------------===//
20+
21+
#include "llvm/Transforms/Instrumentation.h"
22+
#include "llvm/ADT/SmallString.h"
23+
#include "llvm/ADT/SmallVector.h"
24+
#include "llvm/ADT/Statistic.h"
25+
#include "llvm/ADT/StringExtras.h"
26+
#include "llvm/IR/Function.h"
27+
#include "llvm/IR/IRBuilder.h"
28+
#include "llvm/IR/IntrinsicInst.h"
29+
#include "llvm/IR/Module.h"
30+
#include "llvm/IR/Type.h"
31+
#include "llvm/Support/CommandLine.h"
32+
#include "llvm/Support/Debug.h"
33+
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
34+
#include "llvm/Transforms/Utils/ModuleUtils.h"
35+
36+
using namespace llvm;
37+
38+
#define DEBUG_TYPE "esan"
39+
40+
// The tool type must be just one of these ClTool* options, as the tools
41+
// cannot be combined due to shadow memory constraints.
42+
static cl::opt<bool>
43+
ClToolCacheFrag("esan-cache-frag", cl::init(false),
44+
cl::desc("Detect data cache fragmentation"), cl::Hidden);
45+
// Each new tool will get its own opt flag here.
46+
// These are converted to EfficiencySanitizerOptions for use
47+
// in the code.
48+
49+
static cl::opt<bool> ClInstrumentLoadsAndStores(
50+
"esan-instrument-loads-and-stores", cl::init(true),
51+
cl::desc("Instrument loads and stores"), cl::Hidden);
52+
static cl::opt<bool> ClInstrumentMemIntrinsics(
53+
"esan-instrument-memintrinsics", cl::init(true),
54+
cl::desc("Instrument memintrinsics (memset/memcpy/memmove)"), cl::Hidden);
55+
56+
STATISTIC(NumInstrumentedLoads, "Number of instrumented loads");
57+
STATISTIC(NumInstrumentedStores, "Number of instrumented stores");
58+
STATISTIC(NumFastpaths, "Number of instrumented fastpaths");
59+
STATISTIC(NumAccessesWithIrregularSize,
60+
"Number of accesses with a size outside our targeted callout sizes");
61+
62+
static const char *const EsanModuleCtorName = "esan.module_ctor";
63+
static const char *const EsanInitName = "__esan_init";
64+
65+
namespace {
66+
67+
static EfficiencySanitizerOptions
68+
OverrideOptionsFromCL(EfficiencySanitizerOptions Options) {
69+
if (ClToolCacheFrag)
70+
Options.ToolType = EfficiencySanitizerOptions::ESAN_CacheFrag;
71+
72+
// Direct opt invocation with no params will have the default ESAN_None.
73+
// We run the default tool in that case.
74+
if (Options.ToolType == EfficiencySanitizerOptions::ESAN_None)
75+
Options.ToolType = EfficiencySanitizerOptions::ESAN_CacheFrag;
76+
77+
return Options;
78+
}
79+
80+
/// EfficiencySanitizer: instrument each module to find performance issues.
81+
class EfficiencySanitizer : public FunctionPass {
82+
public:
83+
EfficiencySanitizer(
84+
const EfficiencySanitizerOptions &Opts = EfficiencySanitizerOptions())
85+
: FunctionPass(ID), Options(OverrideOptionsFromCL(Opts)) {}
86+
const char *getPassName() const override;
87+
bool runOnFunction(Function &F) override;
88+
bool doInitialization(Module &M) override;
89+
static char ID;
90+
91+
private:
92+
void initializeCallbacks(Module &M);
93+
bool instrumentLoadOrStore(Instruction *I, const DataLayout &DL);
94+
bool instrumentMemIntrinsic(MemIntrinsic *MI);
95+
bool shouldIgnoreMemoryAccess(Instruction *I);
96+
int getMemoryAccessFuncIndex(Value *Addr, const DataLayout &DL);
97+
bool instrumentFastpath(Instruction *I, const DataLayout &DL, bool IsStore,
98+
Value *Addr, unsigned Alignment);
99+
// Each tool has its own fastpath routine:
100+
bool instrumentFastpathCacheFrag(Instruction *I, const DataLayout &DL,
101+
Value *Addr, unsigned Alignment);
102+
103+
EfficiencySanitizerOptions Options;
104+
LLVMContext *Ctx;
105+
Type *IntptrTy;
106+
// Our slowpath involves callouts to the runtime library.
107+
// Access sizes are powers of two: 1, 2, 4, 8, 16.
108+
static const size_t NumberOfAccessSizes = 5;
109+
Function *EsanAlignedLoad[NumberOfAccessSizes];
110+
Function *EsanAlignedStore[NumberOfAccessSizes];
111+
Function *EsanUnalignedLoad[NumberOfAccessSizes];
112+
Function *EsanUnalignedStore[NumberOfAccessSizes];
113+
// For irregular sizes of any alignment:
114+
Function *EsanUnalignedLoadN, *EsanUnalignedStoreN;
115+
Function *MemmoveFn, *MemcpyFn, *MemsetFn;
116+
Function *EsanCtorFunction;
117+
};
118+
} // namespace
119+
120+
char EfficiencySanitizer::ID = 0;
121+
INITIALIZE_PASS(EfficiencySanitizer, "esan",
122+
"EfficiencySanitizer: finds performance issues.", false, false)
123+
124+
const char *EfficiencySanitizer::getPassName() const {
125+
return "EfficiencySanitizer";
126+
}
127+
128+
FunctionPass *
129+
llvm::createEfficiencySanitizerPass(const EfficiencySanitizerOptions &Options) {
130+
return new EfficiencySanitizer(Options);
131+
}
132+
133+
void EfficiencySanitizer::initializeCallbacks(Module &M) {
134+
IRBuilder<> IRB(M.getContext());
135+
// Initialize the callbacks.
136+
for (size_t Idx = 0; Idx < NumberOfAccessSizes; ++Idx) {
137+
const unsigned ByteSize = 1U << Idx;
138+
std::string ByteSizeStr = utostr(ByteSize);
139+
// We'll inline the most common (i.e., aligned and frequent sizes)
140+
// load + store instrumentation: these callouts are for the slowpath.
141+
SmallString<32> AlignedLoadName("__esan_aligned_load" + ByteSizeStr);
142+
EsanAlignedLoad[Idx] =
143+
checkSanitizerInterfaceFunction(M.getOrInsertFunction(
144+
AlignedLoadName, IRB.getVoidTy(), IRB.getInt8PtrTy(), nullptr));
145+
SmallString<32> AlignedStoreName("__esan_aligned_store" + ByteSizeStr);
146+
EsanAlignedStore[Idx] =
147+
checkSanitizerInterfaceFunction(M.getOrInsertFunction(
148+
AlignedStoreName, IRB.getVoidTy(), IRB.getInt8PtrTy(), nullptr));
149+
SmallString<32> UnalignedLoadName("__esan_unaligned_load" + ByteSizeStr);
150+
EsanUnalignedLoad[Idx] =
151+
checkSanitizerInterfaceFunction(M.getOrInsertFunction(
152+
UnalignedLoadName, IRB.getVoidTy(), IRB.getInt8PtrTy(), nullptr));
153+
SmallString<32> UnalignedStoreName("__esan_unaligned_store" + ByteSizeStr);
154+
EsanUnalignedStore[Idx] =
155+
checkSanitizerInterfaceFunction(M.getOrInsertFunction(
156+
UnalignedStoreName, IRB.getVoidTy(), IRB.getInt8PtrTy(), nullptr));
157+
}
158+
EsanUnalignedLoadN = checkSanitizerInterfaceFunction(
159+
M.getOrInsertFunction("__esan_unaligned_loadN", IRB.getVoidTy(),
160+
IRB.getInt8PtrTy(), IntptrTy, nullptr));
161+
EsanUnalignedStoreN = checkSanitizerInterfaceFunction(
162+
M.getOrInsertFunction("__esan_unaligned_storeN", IRB.getVoidTy(),
163+
IRB.getInt8PtrTy(), IntptrTy, nullptr));
164+
MemmoveFn = checkSanitizerInterfaceFunction(
165+
M.getOrInsertFunction("memmove", IRB.getInt8PtrTy(), IRB.getInt8PtrTy(),
166+
IRB.getInt8PtrTy(), IntptrTy, nullptr));
167+
MemcpyFn = checkSanitizerInterfaceFunction(
168+
M.getOrInsertFunction("memcpy", IRB.getInt8PtrTy(), IRB.getInt8PtrTy(),
169+
IRB.getInt8PtrTy(), IntptrTy, nullptr));
170+
MemsetFn = checkSanitizerInterfaceFunction(
171+
M.getOrInsertFunction("memset", IRB.getInt8PtrTy(), IRB.getInt8PtrTy(),
172+
IRB.getInt32Ty(), IntptrTy, nullptr));
173+
}
174+
175+
bool EfficiencySanitizer::doInitialization(Module &M) {
176+
Ctx = &M.getContext();
177+
const DataLayout &DL = M.getDataLayout();
178+
IRBuilder<> IRB(M.getContext());
179+
IntegerType *OrdTy = IRB.getInt32Ty();
180+
IntptrTy = DL.getIntPtrType(M.getContext());
181+
std::tie(EsanCtorFunction, std::ignore) = createSanitizerCtorAndInitFunctions(
182+
M, EsanModuleCtorName, EsanInitName, /*InitArgTypes=*/{OrdTy},
183+
/*InitArgs=*/{
184+
ConstantInt::get(OrdTy, static_cast<int>(Options.ToolType))});
185+
186+
appendToGlobalCtors(M, EsanCtorFunction, 0);
187+
188+
return true;
189+
}
190+
191+
bool EfficiencySanitizer::shouldIgnoreMemoryAccess(Instruction *I) {
192+
if (Options.ToolType == EfficiencySanitizerOptions::ESAN_CacheFrag) {
193+
// We'd like to know about cache fragmentation in vtable accesses and
194+
// constant data references, so we do not currently ignore anything.
195+
return false;
196+
}
197+
// TODO(bruening): future tools will be returning true for some cases.
198+
return false;
199+
}
200+
201+
bool EfficiencySanitizer::runOnFunction(Function &F) {
202+
// This is required to prevent instrumenting the call to __esan_init from
203+
// within the module constructor.
204+
if (&F == EsanCtorFunction)
205+
return false;
206+
// As a function pass, we must re-initialize every time.
207+
initializeCallbacks(*F.getParent());
208+
SmallVector<Instruction *, 8> LoadsAndStores;
209+
SmallVector<Instruction *, 8> MemIntrinCalls;
210+
bool Res = false;
211+
const DataLayout &DL = F.getParent()->getDataLayout();
212+
213+
for (auto &BB : F) {
214+
for (auto &Inst : BB) {
215+
if ((isa<LoadInst>(Inst) || isa<StoreInst>(Inst) ||
216+
isa<AtomicRMWInst>(Inst) || isa<AtomicCmpXchgInst>(Inst)) &&
217+
!shouldIgnoreMemoryAccess(&Inst))
218+
LoadsAndStores.push_back(&Inst);
219+
else if (isa<MemIntrinsic>(Inst))
220+
MemIntrinCalls.push_back(&Inst);
221+
}
222+
}
223+
224+
if (ClInstrumentLoadsAndStores) {
225+
for (auto Inst : LoadsAndStores) {
226+
Res |= instrumentLoadOrStore(Inst, DL);
227+
}
228+
}
229+
230+
if (ClInstrumentMemIntrinsics) {
231+
for (auto Inst : MemIntrinCalls) {
232+
Res |= instrumentMemIntrinsic(cast<MemIntrinsic>(Inst));
233+
}
234+
}
235+
236+
return Res;
237+
}
238+
239+
bool EfficiencySanitizer::instrumentLoadOrStore(Instruction *I,
240+
const DataLayout &DL) {
241+
IRBuilder<> IRB(I);
242+
bool IsStore;
243+
Value *Addr;
244+
unsigned Alignment;
245+
if (LoadInst *Load = dyn_cast<LoadInst>(I)) {
246+
IsStore = false;
247+
Alignment = Load->getAlignment();
248+
Addr = Load->getPointerOperand();
249+
} else if (StoreInst *Store = dyn_cast<StoreInst>(I)) {
250+
IsStore = true;
251+
Alignment = Store->getAlignment();
252+
Addr = Store->getPointerOperand();
253+
} else if (AtomicRMWInst *RMW = dyn_cast<AtomicRMWInst>(I)) {
254+
IsStore = true;
255+
Alignment = 0;
256+
Addr = RMW->getPointerOperand();
257+
} else if (AtomicCmpXchgInst *Xchg = dyn_cast<AtomicCmpXchgInst>(I)) {
258+
IsStore = true;
259+
Alignment = 0;
260+
Addr = Xchg->getPointerOperand();
261+
} else
262+
llvm_unreachable("Unsupported mem access type");
263+
264+
Type *OrigTy = cast<PointerType>(Addr->getType())->getElementType();
265+
const uint32_t TypeSizeBytes = DL.getTypeStoreSizeInBits(OrigTy) / 8;
266+
Value *OnAccessFunc = nullptr;
267+
if (IsStore)
268+
NumInstrumentedStores++;
269+
else
270+
NumInstrumentedLoads++;
271+
int Idx = getMemoryAccessFuncIndex(Addr, DL);
272+
if (Idx < 0) {
273+
OnAccessFunc = IsStore ? EsanUnalignedStoreN : EsanUnalignedLoadN;
274+
IRB.CreateCall(OnAccessFunc,
275+
{IRB.CreatePointerCast(Addr, IRB.getInt8PtrTy()),
276+
ConstantInt::get(IntptrTy, TypeSizeBytes)});
277+
} else {
278+
if (instrumentFastpath(I, DL, IsStore, Addr, Alignment)) {
279+
NumFastpaths++;
280+
return true;
281+
}
282+
if (Alignment == 0 || Alignment >= 8 || (Alignment % TypeSizeBytes) == 0)
283+
OnAccessFunc = IsStore ? EsanAlignedStore[Idx] : EsanAlignedLoad[Idx];
284+
else
285+
OnAccessFunc = IsStore ? EsanUnalignedStore[Idx] : EsanUnalignedLoad[Idx];
286+
IRB.CreateCall(OnAccessFunc,
287+
IRB.CreatePointerCast(Addr, IRB.getInt8PtrTy()));
288+
}
289+
return true;
290+
}
291+
292+
// It's simplest to replace the memset/memmove/memcpy intrinsics with
293+
// calls that the runtime library intercepts.
294+
// Our pass is late enough that calls should not turn back into intrinsics.
295+
bool EfficiencySanitizer::instrumentMemIntrinsic(MemIntrinsic *MI) {
296+
IRBuilder<> IRB(MI);
297+
bool Res = false;
298+
if (isa<MemSetInst>(MI)) {
299+
IRB.CreateCall(
300+
MemsetFn,
301+
{IRB.CreatePointerCast(MI->getArgOperand(0), IRB.getInt8PtrTy()),
302+
IRB.CreateIntCast(MI->getArgOperand(1), IRB.getInt32Ty(), false),
303+
IRB.CreateIntCast(MI->getArgOperand(2), IntptrTy, false)});
304+
MI->eraseFromParent();
305+
Res = true;
306+
} else if (isa<MemTransferInst>(MI)) {
307+
IRB.CreateCall(
308+
isa<MemCpyInst>(MI) ? MemcpyFn : MemmoveFn,
309+
{IRB.CreatePointerCast(MI->getArgOperand(0), IRB.getInt8PtrTy()),
310+
IRB.CreatePointerCast(MI->getArgOperand(1), IRB.getInt8PtrTy()),
311+
IRB.CreateIntCast(MI->getArgOperand(2), IntptrTy, false)});
312+
MI->eraseFromParent();
313+
Res = true;
314+
} else
315+
llvm_unreachable("Unsupported mem intrinsic type");
316+
return Res;
317+
}
318+
319+
int EfficiencySanitizer::getMemoryAccessFuncIndex(Value *Addr,
320+
const DataLayout &DL) {
321+
Type *OrigPtrTy = Addr->getType();
322+
Type *OrigTy = cast<PointerType>(OrigPtrTy)->getElementType();
323+
assert(OrigTy->isSized());
324+
// The size is always a multiple of 8.
325+
uint32_t TypeSizeBytes = DL.getTypeStoreSizeInBits(OrigTy) / 8;
326+
if (TypeSizeBytes != 1 && TypeSizeBytes != 2 && TypeSizeBytes != 4 &&
327+
TypeSizeBytes != 8 && TypeSizeBytes != 16) {
328+
// Irregular sizes do not have per-size call targets.
329+
NumAccessesWithIrregularSize++;
330+
return -1;
331+
}
332+
size_t Idx = countTrailingZeros(TypeSizeBytes);
333+
assert(Idx < NumberOfAccessSizes);
334+
return Idx;
335+
}
336+
337+
bool EfficiencySanitizer::instrumentFastpath(Instruction *I,
338+
const DataLayout &DL, bool IsStore,
339+
Value *Addr, unsigned Alignment) {
340+
if (Options.ToolType == EfficiencySanitizerOptions::ESAN_CacheFrag) {
341+
return instrumentFastpathCacheFrag(I, DL, Addr, Alignment);
342+
}
343+
return false;
344+
}
345+
346+
bool EfficiencySanitizer::instrumentFastpathCacheFrag(Instruction *I,
347+
const DataLayout &DL,
348+
Value *Addr,
349+
unsigned Alignment) {
350+
// TODO(bruening): implement a fastpath for aligned accesses
351+
return false;
352+
}

llvm/lib/Transforms/Instrumentation/Instrumentation.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ void llvm::initializeInstrumentation(PassRegistry &Registry) {
6767
initializeThreadSanitizerPass(Registry);
6868
initializeSanitizerCoverageModulePass(Registry);
6969
initializeDataFlowSanitizerPass(Registry);
70+
initializeEfficiencySanitizerPass(Registry);
7071
}
7172

7273
/// LLVMInitializeInstrumentation - C binding for

0 commit comments

Comments
 (0)