mirror of
https://github.com/c64scene-ar/llvm-6502.git
synced 2024-08-26 07:29:55 +00:00
Break the arc-profile code out to a function like the notes emission code is, and reorder the functions in the file. The only functionality change is that we no longer modify the Module when the Module has no debug info to use. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@129631 91177308-0d34-0410-b5e6-96231b3b80d8
519 lines
17 KiB
C++
519 lines
17 KiB
C++
//===- GCOVProfiling.cpp - Insert edge counters for gcov profiling --------===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This pass implements GCOV-style profiling. When this pass is run it emits
|
|
// "gcno" files next to the existing source, and instruments the code that runs
|
|
// to records the edges between blocks that run and emit a complementary "gcda"
|
|
// file on exit.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#define DEBUG_TYPE "insert-gcov-profiling"
|
|
|
|
#include "ProfilingUtils.h"
|
|
#include "llvm/Transforms/Instrumentation.h"
|
|
#include "llvm/Analysis/DebugInfo.h"
|
|
#include "llvm/Module.h"
|
|
#include "llvm/Pass.h"
|
|
#include "llvm/Instructions.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/Support/DebugLoc.h"
|
|
#include "llvm/Support/InstIterator.h"
|
|
#include "llvm/Support/IRBuilder.h"
|
|
#include "llvm/Support/PathV2.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/ADT/Statistic.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/ADT/StringMap.h"
|
|
#include "llvm/ADT/UniqueVector.h"
|
|
#include <string>
|
|
#include <utility>
|
|
using namespace llvm;
|
|
|
|
namespace {
|
|
class GCOVProfiler : public ModulePass {
|
|
bool runOnModule(Module &M);
|
|
public:
|
|
static char ID;
|
|
GCOVProfiler() : ModulePass(ID) {
|
|
initializeGCOVProfilerPass(*PassRegistry::getPassRegistry());
|
|
}
|
|
virtual const char *getPassName() const {
|
|
return "GCOV Profiler";
|
|
}
|
|
|
|
private:
|
|
// Create the GCNO files for the Module based on DebugInfo.
|
|
void EmitGCNO(DebugInfoFinder &DIF);
|
|
|
|
// Modify the program to track transitions along edges and call into the
|
|
// profiling runtime to emit .gcda files when run.
|
|
bool EmitProfileArcs(DebugInfoFinder &DIF);
|
|
|
|
// Get pointers to the functions in the runtime library.
|
|
Constant *getStartFileFunc();
|
|
Constant *getEmitFunctionFunc();
|
|
Constant *getEmitArcsFunc();
|
|
Constant *getEndFileFunc();
|
|
|
|
// Add the function to write out all our counters to the global destructor
|
|
// list.
|
|
void InsertCounterWriteout(DebugInfoFinder &,
|
|
SmallVector<std::pair<GlobalVariable *,
|
|
uint32_t>, 8> &);
|
|
|
|
Module *Mod;
|
|
LLVMContext *Ctx;
|
|
};
|
|
}
|
|
|
|
char GCOVProfiler::ID = 0;
|
|
INITIALIZE_PASS(GCOVProfiler, "insert-gcov-profiling",
|
|
"Insert instrumentation for GCOV profiling", false, false)
|
|
|
|
ModulePass *llvm::createGCOVProfilerPass() { return new GCOVProfiler(); }
|
|
|
|
static DISubprogram FindSubprogram(DIScope scope) {
|
|
while (!scope.isSubprogram()) {
|
|
assert(scope.isLexicalBlock() &&
|
|
"Debug location not lexical block or subprogram");
|
|
scope = DILexicalBlock(scope).getContext();
|
|
}
|
|
return DISubprogram(scope);
|
|
}
|
|
|
|
namespace {
|
|
class GCOVRecord {
|
|
protected:
|
|
static const char *lines_tag;
|
|
static const char *function_tag;
|
|
static const char *block_tag;
|
|
static const char *edge_tag;
|
|
|
|
GCOVRecord() {}
|
|
|
|
void WriteBytes(const char *b, int size) {
|
|
os->write(b, size);
|
|
}
|
|
|
|
void Write(uint32_t i) {
|
|
WriteBytes(reinterpret_cast<char*>(&i), 4);
|
|
}
|
|
|
|
// Returns the length measured in 4-byte blocks that will be used to
|
|
// represent this string in a GCOV file
|
|
unsigned LengthOfGCOVString(StringRef s) {
|
|
// A GCOV string is a length, followed by a NUL, then between 0 and 3 NULs
|
|
// padding out to the next 4-byte word. The length is measured in 4-byte words
|
|
// including padding, not bytes of actual string.
|
|
return (s.size() + 5) / 4;
|
|
}
|
|
|
|
void WriteGCOVString(StringRef s) {
|
|
uint32_t len = LengthOfGCOVString(s);
|
|
Write(len);
|
|
WriteBytes(s.data(), s.size());
|
|
|
|
// Write 1 to 4 bytes of NUL padding.
|
|
assert((unsigned)(5 - ((s.size() + 1) % 4)) > 0);
|
|
assert((unsigned)(5 - ((s.size() + 1) % 4)) <= 4);
|
|
WriteBytes("\0\0\0\0", 5 - ((s.size() + 1) % 4));
|
|
}
|
|
|
|
raw_ostream *os;
|
|
};
|
|
const char *GCOVRecord::lines_tag = "\0\0\x45\x01";
|
|
const char *GCOVRecord::function_tag = "\0\0\0\1";
|
|
const char *GCOVRecord::block_tag = "\0\0\x41\x01";
|
|
const char *GCOVRecord::edge_tag = "\0\0\x43\x01";
|
|
|
|
class GCOVFunction;
|
|
class GCOVBlock;
|
|
|
|
// Constructed only by requesting it from a GCOVBlock, this object stores a
|
|
// list of line numbers and a single filename, representing lines that belong
|
|
// to the block.
|
|
class GCOVLines : public GCOVRecord {
|
|
public:
|
|
void AddLine(uint32_t line) {
|
|
lines.push_back(line);
|
|
}
|
|
|
|
uint32_t Length() {
|
|
return LengthOfGCOVString(filename) + 2 + lines.size();
|
|
}
|
|
|
|
private:
|
|
friend class GCOVBlock;
|
|
|
|
GCOVLines(std::string filename, raw_ostream *os)
|
|
: filename(filename) {
|
|
this->os = os;
|
|
}
|
|
|
|
std::string filename;
|
|
SmallVector<uint32_t, 32> lines;
|
|
};
|
|
|
|
// Represent a basic block in GCOV. Each block has a unique number in the
|
|
// function, number of lines belonging to each block, and a set of edges to
|
|
// other blocks.
|
|
class GCOVBlock : public GCOVRecord {
|
|
public:
|
|
GCOVLines &GetFile(std::string filename) {
|
|
GCOVLines *&lines = lines_by_file[filename];
|
|
if (!lines) {
|
|
lines = new GCOVLines(filename, os);
|
|
}
|
|
return *lines;
|
|
}
|
|
|
|
void AddEdge(GCOVBlock &successor) {
|
|
out_edges.push_back(&successor);
|
|
}
|
|
|
|
void WriteOut() {
|
|
uint32_t len = 3;
|
|
for (StringMap<GCOVLines *>::iterator I = lines_by_file.begin(),
|
|
E = lines_by_file.end(); I != E; ++I) {
|
|
len += I->second->Length();
|
|
}
|
|
|
|
WriteBytes(lines_tag, 4);
|
|
Write(len);
|
|
Write(number);
|
|
for (StringMap<GCOVLines *>::iterator I = lines_by_file.begin(),
|
|
E = lines_by_file.end(); I != E; ++I) {
|
|
Write(0);
|
|
WriteGCOVString(I->second->filename);
|
|
for (int i = 0, e = I->second->lines.size(); i != e; ++i) {
|
|
Write(I->second->lines[i]);
|
|
}
|
|
}
|
|
Write(0);
|
|
Write(0);
|
|
}
|
|
|
|
~GCOVBlock() {
|
|
DeleteContainerSeconds(lines_by_file);
|
|
}
|
|
|
|
private:
|
|
friend class GCOVFunction;
|
|
|
|
GCOVBlock(uint32_t number, raw_ostream *os)
|
|
: number(number) {
|
|
this->os = os;
|
|
}
|
|
|
|
uint32_t number;
|
|
BasicBlock *block;
|
|
StringMap<GCOVLines *> lines_by_file;
|
|
SmallVector<GCOVBlock *, 4> out_edges;
|
|
};
|
|
|
|
// A function has a unique identifier, a checksum (we leave as zero) and a
|
|
// set of blocks and a map of edges between blocks. This is the only GCOV
|
|
// object users can construct, the blocks and lines will be rooted here.
|
|
class GCOVFunction : public GCOVRecord {
|
|
public:
|
|
GCOVFunction(DISubprogram SP, raw_ostream *os) {
|
|
this->os = os;
|
|
|
|
Function *F = SP.getFunction();
|
|
uint32_t i = 0;
|
|
for (Function::iterator BB = F->begin(), E = F->end(); BB != E; ++BB) {
|
|
blocks[BB] = new GCOVBlock(i++, os);
|
|
}
|
|
|
|
WriteBytes(function_tag, 4);
|
|
uint32_t block_len = 1 + 1 + 1 + LengthOfGCOVString(SP.getName()) +
|
|
1 + LengthOfGCOVString(SP.getFilename()) + 1;
|
|
Write(block_len);
|
|
uint32_t ident = reinterpret_cast<intptr_t>((MDNode*)SP);
|
|
Write(ident);
|
|
Write(0); // checksum
|
|
WriteGCOVString(SP.getName());
|
|
WriteGCOVString(SP.getFilename());
|
|
Write(SP.getLineNumber());
|
|
}
|
|
|
|
~GCOVFunction() {
|
|
DeleteContainerSeconds(blocks);
|
|
}
|
|
|
|
GCOVBlock &GetBlock(BasicBlock *BB) {
|
|
return *blocks[BB];
|
|
}
|
|
|
|
void WriteOut() {
|
|
// Emit count of blocks.
|
|
WriteBytes(block_tag, 4);
|
|
Write(blocks.size());
|
|
for (int i = 0, e = blocks.size(); i != e; ++i) {
|
|
Write(0); // No flags on our blocks.
|
|
}
|
|
|
|
// Emit edges between blocks.
|
|
for (DenseMap<BasicBlock *, GCOVBlock *>::iterator I = blocks.begin(),
|
|
E = blocks.end(); I != E; ++I) {
|
|
GCOVBlock &block = *I->second;
|
|
if (block.out_edges.empty()) continue;
|
|
|
|
WriteBytes(edge_tag, 4);
|
|
Write(block.out_edges.size() * 2 + 1);
|
|
Write(block.number);
|
|
for (int i = 0, e = block.out_edges.size(); i != e; ++i) {
|
|
Write(block.out_edges[i]->number);
|
|
Write(0); // no flags
|
|
}
|
|
}
|
|
|
|
// Emit lines for each block.
|
|
for (DenseMap<BasicBlock *, GCOVBlock *>::iterator I = blocks.begin(),
|
|
E = blocks.end(); I != E; ++I) {
|
|
I->second->WriteOut();
|
|
}
|
|
}
|
|
|
|
private:
|
|
DenseMap<BasicBlock *, GCOVBlock *> blocks;
|
|
};
|
|
}
|
|
|
|
// Replace the stem of a file, or add one if missing.
|
|
static std::string ReplaceStem(std::string orig_filename, std::string new_stem){
|
|
return (sys::path::stem(orig_filename) + "." + new_stem).str();
|
|
}
|
|
|
|
bool GCOVProfiler::runOnModule(Module &M) {
|
|
Mod = &M;
|
|
Ctx = &M.getContext();
|
|
|
|
DebugInfoFinder DIF;
|
|
DIF.processModule(*Mod);
|
|
|
|
EmitGCNO(DIF);
|
|
return EmitProfileArcs(DIF);
|
|
}
|
|
|
|
void GCOVProfiler::EmitGCNO(DebugInfoFinder &DIF) {
|
|
DenseMap<const MDNode *, raw_fd_ostream *> gcno_files;
|
|
for (DebugInfoFinder::iterator I = DIF.compile_unit_begin(),
|
|
E = DIF.compile_unit_end(); I != E; ++I) {
|
|
// Each compile unit gets its own .gcno file. This means that whether we run
|
|
// this pass over the original .o's as they're produced, or run it after
|
|
// LTO, we'll generate the same .gcno files.
|
|
|
|
DICompileUnit CU(*I);
|
|
raw_fd_ostream *&Out = gcno_files[CU];
|
|
std::string ErrorInfo;
|
|
Out = new raw_fd_ostream(ReplaceStem(CU.getFilename(), "gcno").c_str(),
|
|
ErrorInfo, raw_fd_ostream::F_Binary);
|
|
Out->write("oncg*404MVLL", 12);
|
|
}
|
|
|
|
for (DebugInfoFinder::iterator SPI = DIF.subprogram_begin(),
|
|
SPE = DIF.subprogram_end(); SPI != SPE; ++SPI) {
|
|
DISubprogram SP(*SPI);
|
|
raw_fd_ostream *&os = gcno_files[SP.getCompileUnit()];
|
|
|
|
GCOVFunction function(SP, os);
|
|
Function *F = SP.getFunction();
|
|
for (Function::iterator BB = F->begin(), E = F->end(); BB != E; ++BB) {
|
|
GCOVBlock &block = function.GetBlock(BB);
|
|
TerminatorInst *TI = BB->getTerminator();
|
|
if (int successors = TI->getNumSuccessors()) {
|
|
for (int i = 0; i != successors; ++i) {
|
|
block.AddEdge(function.GetBlock(TI->getSuccessor(i)));
|
|
}
|
|
}
|
|
|
|
uint32_t line = 0;
|
|
for (BasicBlock::iterator I = BB->begin(), IE = BB->end(); I != IE; ++I) {
|
|
const DebugLoc &loc = I->getDebugLoc();
|
|
if (loc.isUnknown()) continue;
|
|
if (line == loc.getLine()) continue;
|
|
line = loc.getLine();
|
|
if (SP != FindSubprogram(DIScope(loc.getScope(*Ctx)))) continue;
|
|
|
|
GCOVLines &lines = block.GetFile(SP.getFilename());
|
|
lines.AddLine(loc.getLine());
|
|
}
|
|
}
|
|
function.WriteOut();
|
|
}
|
|
|
|
for (DenseMap<const MDNode *, raw_fd_ostream *>::iterator
|
|
I = gcno_files.begin(), E = gcno_files.end(); I != E; ++I) {
|
|
raw_fd_ostream *&Out = I->second;
|
|
Out->write("\0\0\0\0\0\0\0\0", 4); // EOF
|
|
Out->close();
|
|
delete Out;
|
|
}
|
|
}
|
|
|
|
bool GCOVProfiler::EmitProfileArcs(DebugInfoFinder &DIF) {
|
|
if (DIF.subprogram_begin() == DIF.subprogram_end())
|
|
return false;
|
|
|
|
SmallVector<std::pair<GlobalVariable *, uint32_t>, 8> counters_by_ident;
|
|
for (DebugInfoFinder::iterator SPI = DIF.subprogram_begin(),
|
|
SPE = DIF.subprogram_end(); SPI != SPE; ++SPI) {
|
|
DISubprogram SP(*SPI);
|
|
Function *F = SP.getFunction();
|
|
|
|
// TODO: GCOV format requires a distinct unified exit block.
|
|
unsigned edges = 0;
|
|
for (Function::iterator BB = F->begin(), E = F->end(); BB != E; ++BB) {
|
|
TerminatorInst *TI = BB->getTerminator();
|
|
edges += TI->getNumSuccessors();
|
|
}
|
|
|
|
const ArrayType *counter_type =
|
|
ArrayType::get(Type::getInt64Ty(*Ctx), edges);
|
|
GlobalVariable *counter =
|
|
new GlobalVariable(*Mod, counter_type, false,
|
|
GlobalValue::InternalLinkage,
|
|
Constant::getNullValue(counter_type),
|
|
"__llvm_gcov_ctr", 0, false, 0);
|
|
counters_by_ident.push_back(
|
|
std::make_pair(counter, reinterpret_cast<intptr_t>((MDNode*)SP)));
|
|
|
|
UniqueVector<BasicBlock *> complex_edge_preds;
|
|
UniqueVector<BasicBlock *> complex_edge_succs;
|
|
|
|
unsigned edge_num = 0;
|
|
for (Function::iterator BB = F->begin(), E = F->end(); BB != E; ++BB) {
|
|
TerminatorInst *TI = BB->getTerminator();
|
|
if (int successors = TI->getNumSuccessors()) {
|
|
IRBuilder<> builder(TI);
|
|
|
|
if (successors == 1) {
|
|
Value *ctr = builder.CreateConstInBoundsGEP2_64(counter, 0, edge_num);
|
|
Value *count = builder.CreateLoad(ctr);
|
|
count = builder.CreateAdd(count,
|
|
ConstantInt::get(Type::getInt64Ty(*Ctx),1));
|
|
builder.CreateStore(count, ctr);
|
|
} else if (BranchInst *BI = dyn_cast<BranchInst>(TI)) {
|
|
Value *sel = builder.CreateSelect(
|
|
BI->getCondition(),
|
|
ConstantInt::get(Type::getInt64Ty(*Ctx), edge_num),
|
|
ConstantInt::get(Type::getInt64Ty(*Ctx), edge_num + 1));
|
|
SmallVector<Value *, 2> idx;
|
|
idx.push_back(Constant::getNullValue(Type::getInt64Ty(*Ctx)));
|
|
idx.push_back(sel);
|
|
Value *ctr = builder.CreateInBoundsGEP(counter,
|
|
idx.begin(), idx.end());
|
|
Value *count = builder.CreateLoad(ctr);
|
|
count = builder.CreateAdd(count,
|
|
ConstantInt::get(Type::getInt64Ty(*Ctx),1));
|
|
builder.CreateStore(count, ctr);
|
|
} else {
|
|
complex_edge_preds.insert(BB);
|
|
for (int i = 0; i != successors; ++i) {
|
|
complex_edge_succs.insert(TI->getSuccessor(i));
|
|
}
|
|
}
|
|
edge_num += successors;
|
|
}
|
|
}
|
|
|
|
// TODO: support switch, invoke, indirectbr
|
|
if (!complex_edge_preds.empty()) {
|
|
// emit a [preds x [succs x i64*]].
|
|
for (int i = 0, e = complex_edge_preds.size(); i != e; ++i) {
|
|
// call runtime to state save
|
|
}
|
|
for (int i = 0, e = complex_edge_succs.size(); i != e; ++i) {
|
|
// call runtime to perform increment
|
|
}
|
|
}
|
|
}
|
|
|
|
InsertCounterWriteout(DIF, counters_by_ident);
|
|
|
|
return true;
|
|
}
|
|
|
|
Constant *GCOVProfiler::getStartFileFunc() {
|
|
const Type *Args[1] = { Type::getInt8PtrTy(*Ctx) };
|
|
const FunctionType *FTy = FunctionType::get(Type::getVoidTy(*Ctx),
|
|
Args, false);
|
|
return Mod->getOrInsertFunction("llvm_gcda_start_file", FTy);
|
|
}
|
|
|
|
Constant *GCOVProfiler::getEmitFunctionFunc() {
|
|
const Type *Args[1] = { Type::getInt32Ty(*Ctx) };
|
|
const FunctionType *FTy = FunctionType::get(Type::getVoidTy(*Ctx),
|
|
Args, false);
|
|
return Mod->getOrInsertFunction("llvm_gcda_emit_function", FTy);
|
|
}
|
|
|
|
Constant *GCOVProfiler::getEmitArcsFunc() {
|
|
const Type *Args[] = {
|
|
Type::getInt32Ty(*Ctx), // uint32_t num_counters
|
|
Type::getInt64PtrTy(*Ctx), // uint64_t *counters
|
|
};
|
|
const FunctionType *FTy = FunctionType::get(Type::getVoidTy(*Ctx),
|
|
Args, false);
|
|
return Mod->getOrInsertFunction("llvm_gcda_emit_arcs", FTy);
|
|
}
|
|
|
|
Constant *GCOVProfiler::getEndFileFunc() {
|
|
const FunctionType *FTy = FunctionType::get(Type::getVoidTy(*Ctx), false);
|
|
return Mod->getOrInsertFunction("llvm_gcda_end_file", FTy);
|
|
}
|
|
|
|
void GCOVProfiler::InsertCounterWriteout(
|
|
DebugInfoFinder &DIF,
|
|
SmallVector<std::pair<GlobalVariable *, uint32_t>, 8> &counters_by_ident) {
|
|
|
|
const FunctionType *WriteoutFTy =
|
|
FunctionType::get(Type::getVoidTy(*Ctx), false);
|
|
Function *WriteoutF = Function::Create(WriteoutFTy,
|
|
GlobalValue::InternalLinkage,
|
|
"__llvm_gcda_writeout", Mod);
|
|
WriteoutF->setUnnamedAddr(true);
|
|
BasicBlock *BB = BasicBlock::Create(*Ctx, "", WriteoutF);
|
|
IRBuilder<> builder(BB);
|
|
|
|
Constant *StartFile = getStartFileFunc();
|
|
Constant *EmitFunction = getEmitFunctionFunc();
|
|
Constant *EmitArcs = getEmitArcsFunc();
|
|
Constant *EndFile = getEndFileFunc();
|
|
|
|
for (DebugInfoFinder::iterator CUI = DIF.compile_unit_begin(),
|
|
CUE = DIF.compile_unit_end(); CUI != CUE; ++CUI) {
|
|
DICompileUnit compile_unit(*CUI);
|
|
std::string filename_gcda = ReplaceStem(compile_unit.getFilename(), "gcda");
|
|
builder.CreateCall(StartFile,
|
|
builder.CreateGlobalStringPtr(filename_gcda));
|
|
for (SmallVector<std::pair<GlobalVariable *, uint32_t>, 8>::iterator
|
|
I = counters_by_ident.begin(), E = counters_by_ident.end();
|
|
I != E; ++I) {
|
|
builder.CreateCall(EmitFunction, ConstantInt::get(Type::getInt32Ty(*Ctx),
|
|
I->second));
|
|
GlobalVariable *GV = I->first;
|
|
unsigned num_arcs =
|
|
cast<ArrayType>(GV->getType()->getElementType())->getNumElements();
|
|
builder.CreateCall2(
|
|
EmitArcs,
|
|
ConstantInt::get(Type::getInt32Ty(*Ctx), num_arcs),
|
|
builder.CreateConstGEP2_64(GV, 0, 0));
|
|
}
|
|
builder.CreateCall(EndFile);
|
|
}
|
|
builder.CreateRetVoid();
|
|
|
|
InsertProfilingShutdownCall(WriteoutF, Mod);
|
|
}
|