Skip to content

Commit

Permalink
Merge pull request #878 from JohanEngelen/coverage
Browse files Browse the repository at this point in the history
Add Coverage Analysis (à la DMD)
  • Loading branch information
redstar committed Apr 8, 2015
2 parents 014627d + 505f18c commit b1ff093
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 9 deletions.
5 changes: 3 additions & 2 deletions dmd2/mars.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,6 @@ struct Param
#else
bool pic; // generate position-independent-code for shared libs
bool color; // use ANSI colors in console output
bool cov; // generate code coverage data
unsigned char covPercent; // 0..100 code coverage percentage required
bool nofloat; // code should not pull in floating point support
bool ignoreUnsupportedPragmas; // rather than error on them
bool enforcePropertySyntax;
Expand All @@ -180,6 +178,9 @@ struct Param
bool allInst; // generate code for all template instantiations
#endif

bool cov; // generate code coverage data
unsigned char covPercent; // 0..100 code coverage percentage required

const char *argv0; // program name
Strings *imppath; // array of char*'s of where to look for import modules
Strings *fileImppath; // array of char*'s of where to look for file import modules
Expand Down
2 changes: 2 additions & 0 deletions dmd2/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ Module::Module(const char *filename, Identifier *ident, int doDocComment, int do
this->doDocComment = doDocComment;
this->doHdrGen = doHdrGen;
this->arrayfuncs = 0;
d_cover_valid = NULL;
d_cover_data = NULL;
#endif
}

Expand Down
5 changes: 5 additions & 0 deletions dmd2/module.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ class Module : public Package

// array ops emitted in this module already
AA *arrayfuncs;

// Coverage analysis
llvm::GlobalVariable* d_cover_valid; // private immutable size_t[] _d_cover_valid;
llvm::GlobalVariable* d_cover_data; // private uint[] _d_cover_data;
std::vector<size_t> d_cover_valid_init; // initializer for _d_cover_valid
#endif

Module *isModule() { return this; }
Expand Down
34 changes: 34 additions & 0 deletions driver/cl_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,34 @@

namespace opts {

/* Option parser that defaults to zero when no explicit number is given.
* i.e.: -cov --> value = 0
* -cov=9 --> value = 9
* -cov=101 --> error, value must be in range [0..100]
*/
struct CoverageParser : public cl::parser<unsigned char> {
#if LDC_LLVM_VER >= 307
CoverageParser(cl::Option &O) : cl::parser<unsigned char>(O) {}
#endif

bool parse(cl::Option &O, llvm::StringRef ArgName, llvm::StringRef Arg, unsigned char &Val)
{
if (Arg == "") {
Val = 0;
return false;
}

if (Arg.getAsInteger(0, Val))
return O.error("'" + Arg + "' value invalid for required coverage percentage");

if (Val > 100) {
return O.error("Required coverage percentage must be <= 100");
}
return false;
}
};


// Positional options first, in order:
cl::list<std::string> fileList(
cl::Positional, cl::desc("files"));
Expand Down Expand Up @@ -412,6 +440,12 @@ cl::opt<bool, true, FlagParser> color("color",
cl::desc("Force colored console output"),
cl::location(global.params.color));

cl::opt<unsigned char, true, CoverageParser> coverageAnalysis("cov",
cl::desc("Compile-in code coverage analysis\n(use -cov=n for n% minimum required coverage)"),
cl::location(global.params.covPercent),
cl::ValueOptional,
cl::init(127));

static cl::extrahelp footer("\n"
"-d-debug can also be specified without options, in which case it enables all\n"
"debug checks (i.e. (asserts, boundchecks, contracts and invariants) as well\n"
Expand Down
16 changes: 9 additions & 7 deletions driver/ldmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,10 @@ Usage:\n\
files.d D source files\n\
@cmdfile read arguments from cmdfile\n\
-c do not link\n\
-color[=on|off] force colored console output on or off\n"
#if 0
" -cov do code coverage analysis\n"
#endif
" -D generate documentation\n\
-color[=on|off] force colored console output on or off\n\
-cov do code coverage analysis\n\
-cov=nnn require at least nnn%% code coverage\n\
-D generate documentation\n\
-Dddocdir write documentation file to docdir directory\n\
-Dffilename write documentation file to filename\n\
-d allow deprecated features\n\
Expand Down Expand Up @@ -599,7 +598,10 @@ Params parseArgs(size_t originalArgc, char** originalArgv, const std::string &ld
goto Lerror;
}
else if (strcmp(p + 1, "cov") == 0)
result.coverage = true;
// For "-cov=...", the whole cmdline switch is forwarded to LDC.
// For plain "-cov", the cmdline switch must be explicitly forwarded
// and result.coverage must be set to true to that effect.
result.coverage = (p[4] != '=');
else if (strcmp(p + 1, "shared") == 0
// backwards compatibility with old switch
|| strcmp(p + 1, "dylib") == 0
Expand Down Expand Up @@ -935,7 +937,7 @@ void buildCommandLine(std::vector<const char*>& r, const Params& p)
{
if (p.allowDeprecated) r.push_back("-d");
if (p.compileOnly) r.push_back("-c");
if (p.coverage) warning("Coverage report generation not yet supported by LDC.");
if (p.coverage) r.push_back("-cov");
if (p.emitSharedLib) r.push_back("-shared");
if (p.pic) r.push_back("-relocation-model=pic");
if (p.emitMap) warning("Map file generation not yet supported by LDC.");
Expand Down
2 changes: 2 additions & 0 deletions driver/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,8 @@ static void parseCommandLine(int argc, char **argv, Strings &sourceFiles, bool &
global.params.output_ll = opts::output_ll ? OUTPUTFLAGset : OUTPUTFLAGno;
global.params.output_s = opts::output_s ? OUTPUTFLAGset : OUTPUTFLAGno;

global.params.cov = (global.params.covPercent <= 100);

templateLinkage =
opts::linkonceTemplates ? LLGlobalValue::LinkOnceODRLinkage
: LLGlobalValue::WeakODRLinkage;
Expand Down
51 changes: 51 additions & 0 deletions gen/coverage.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//===-- gen/coverage.h - Code Coverage Analysis -----------------*- C++ -*-===//
//
// LDC – the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//

#include "gen/coverage.h"

#include "mars.h"
#include "module.h"
#include "gen/irstate.h"
#include "gen/logger.h"

void emitCoverageLinecountInc(Loc &loc) {
// Only emit coverage increment for locations in the source of the current module
// (for example, 'inlined' methods from other source files should be skipped).
if ( global.params.cov && (loc.linnum != 0) && loc.filename
&& (gIR->module->getModuleIdentifier().compare(loc.filename) == 0) )
{
unsigned line = loc.linnum-1; // convert to 0-based line# index
assert(line < gIR->dmodule->numlines);
{
IF_LOG Logger::println("Coverage: increment _d_cover_data[%d]", line);

// Get GEP into _d_cover_data array
LLConstant* idxs[] = { DtoConstUint(0), DtoConstUint(line) };
LLValue* ptr = llvm::ConstantExpr::getGetElementPtr(gIR->dmodule->d_cover_data, idxs, true);

// Do an atomic increment, so this works when multiple threads are executed.
gIR->ir->CreateAtomicRMW(
llvm::AtomicRMWInst::Add,
ptr,
DtoConstUint(1),
llvm::Monotonic
);
}

{
unsigned num_sizet_bits = gDataLayout->getTypeSizeInBits(DtoSize_t());
unsigned idx = line / num_sizet_bits;
unsigned bitidx = line % num_sizet_bits;

IF_LOG Logger::println(" _d_cover_valid[%d] |= (1 << %d)", idx, bitidx);

gIR->dmodule->d_cover_valid_init[idx] |= (size_t(1) << bitidx);
}
}
}
22 changes: 22 additions & 0 deletions gen/coverage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//===-- gen/coverage.h - Code Coverage Analysis -----------------*- C++ -*-===//
//
// LDC – the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//
//
// This file contains functions to generate code for code coverage analysis.
// The coverage analysis is enabled by the "-cov" commandline switch.
//
//===----------------------------------------------------------------------===//

#ifndef LDC_GEN_COVERAGE_H
#define LDC_GEN_COVERAGE_H

struct Loc;

void emitCoverageLinecountInc(Loc &loc);

#endif
124 changes: 124 additions & 0 deletions gen/module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,119 @@ static void build_llvm_used_array(IRState* p)
llvmUsed->setSection("llvm.metadata");
}

// Add module-private variables and functions for coverage analysis.
static void addCoverageAnalysis(Module* m)
{
IF_LOG {
Logger::println("Adding coverage analysis for module %s (%d lines)", m->srcfile->toChars(), m->numlines);
Logger::indent();
}

// size_t[# source lines / # bits in sizeTy] _d_cover_valid
LLValue* d_cover_valid_slice = NULL;
{
unsigned Dsizet_bits = gDataLayout->getTypeSizeInBits(DtoSize_t());
size_t array_size = (m->numlines + (Dsizet_bits-1)) / Dsizet_bits; // ceil

// Work around a bug in the interface of druntime's _d_cover_register2
// https://issues.dlang.org/show_bug.cgi?id=14417
// For safety, make the array large enough such that the slice passed to _d_cover_register2 is completely valid.
array_size = m->numlines;

IF_LOG Logger::println("Build private variable: size_t[%d] _d_cover_valid", array_size);

llvm::ArrayType* type = llvm::ArrayType::get(DtoSize_t(), array_size);
llvm::ConstantAggregateZero* zeroinitializer = llvm::ConstantAggregateZero::get(type);
m->d_cover_valid = new llvm::GlobalVariable(*gIR->module, type, true, LLGlobalValue::InternalLinkage, zeroinitializer, "_d_cover_valid");
LLConstant* idxs[] = { DtoConstUint(0), DtoConstUint(0) };
d_cover_valid_slice = DtoConstSlice( DtoConstSize_t(type->getArrayNumElements()),
llvm::ConstantExpr::getGetElementPtr(m->d_cover_valid, idxs, true) );

// Assert that initializer array elements have enough bits
assert(sizeof(m->d_cover_valid_init[0])*8 >= gDataLayout->getTypeSizeInBits(DtoSize_t()));
m->d_cover_valid_init.resize(array_size);
}

// uint[# source lines] _d_cover_data
LLValue* d_cover_data_slice = NULL;
{
IF_LOG Logger::println("Build private variable: uint[%d] _d_cover_data", m->numlines);

LLArrayType* type = LLArrayType::get(LLType::getInt32Ty(gIR->context()), m->numlines);
llvm::ConstantAggregateZero* zeroinitializer = llvm::ConstantAggregateZero::get(type);
m->d_cover_data = new llvm::GlobalVariable(*gIR->module, type, false, LLGlobalValue::InternalLinkage, zeroinitializer, "_d_cover_data");
LLConstant* idxs[] = { DtoConstUint(0), DtoConstUint(0) };
d_cover_data_slice = DtoConstSlice( DtoConstSize_t(type->getArrayNumElements()),
llvm::ConstantExpr::getGetElementPtr(m->d_cover_data, idxs, true) );
}

// Create "static constructor" that calls _d_cover_register2(string filename, size_t[] valid, uint[] data, ubyte minPercent)
// Build ctor name
LLFunction* ctor = NULL;
std::string ctorname = "_D";
ctorname += mangle(gIR->dmodule);
ctorname += "12_coverageanalysisCtor1FZv";
{
IF_LOG Logger::println("Build Coverage Analysis constructor: %s", ctorname.c_str());

LLFunctionType* ctorTy = LLFunctionType::get(LLType::getVoidTy(gIR->context()), std::vector<LLType*>(), false);
ctor = LLFunction::Create(ctorTy, LLGlobalValue::InternalLinkage, ctorname, gIR->module);
ctor->setCallingConv(gABI->callingConv(LINKd));
// Set function attributes. See functions.cpp:DtoDefineFunction()
if (global.params.targetTriple.getArch() == llvm::Triple::x86_64)
{
ctor->addFnAttr(LDC_ATTRIBUTE(UWTable));
}

llvm::BasicBlock* bb = llvm::BasicBlock::Create(gIR->context(), "", ctor);
IRBuilder<> builder(bb);

// Set up call to _d_cover_register2
llvm::Function* fn = LLVM_D_GetRuntimeFunction(Loc(), gIR->module, "_d_cover_register2");
LLValue* args[] = {
getIrModule(m)->fileName->getInitializer(),
d_cover_valid_slice,
d_cover_data_slice,
DtoConstUbyte(global.params.covPercent)
};
// Check if argument types are correct
for (unsigned i = 0; i < 4; ++i) {
assert(args[i]->getType() == fn->getFunctionType()->getParamType(i));
}

llvm::CallInst* call = builder.CreateCall(fn, args);

builder.CreateRetVoid();
}

// Add the ctor to the module's static ctors list. TODO: This is quite the hack.
{
IF_LOG Logger::println("Add %s to module's shared static constructor list", ctorname.c_str());
FuncDeclaration* fd = FuncDeclaration::genCfunc(NULL, Type::tvoid, ctorname.c_str());
fd->linkage = LINKd;
IrFunction* irfunc = getIrFunc(fd, true);
irfunc->func = ctor;
gIR->sharedCtors.push_back(fd);
}

IF_LOG Logger::undent();
}

// Initialize _d_cover_valid for coverage analysis
static void addCoverageAnalysisInitializer(Module* m) {
IF_LOG Logger::println("Adding coverage analysis _d_cover_valid initializer");

size_t array_size = m->d_cover_valid_init.size();

llvm::ArrayType* type = llvm::ArrayType::get(DtoSize_t(), array_size);
std::vector<LLConstant*> arrayInits(array_size);
for (size_t i=0; i<array_size; i++)
{
arrayInits[i] = DtoConstSize_t(m->d_cover_valid_init[i]);
}
m->d_cover_valid->setInitializer(llvm::ConstantArray::get(type, arrayInits));
}

static void codegenModule(Module* m)
{
// debug info
Expand Down Expand Up @@ -667,8 +780,19 @@ llvm::Module* Module::genLLVMModule(llvm::LLVMContext& context)

LLVM_D_InitRuntime();

// Note, skip pseudo-modules for coverage analysis
if ( global.params.cov && !mname.equals("__entrypoint.d") && !mname.equals("__main.d") )
{
addCoverageAnalysis(this);
}

codegenModule(this);

if ( gIR->dmodule->d_cover_valid )
{
addCoverageAnalysisInitializer(this);
}

gIR = NULL;

if (llvmForceLogging && !logenabled)
Expand Down
17 changes: 17 additions & 0 deletions gen/runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1021,4 +1021,21 @@ static void LLVM_D_BuildRuntimeModule()
llvm::FunctionType* fty = llvm::FunctionType::get(voidTy, params, false);
llvm::Function::Create(fty, llvm::GlobalValue::ExternalLinkage, fname, M);
}

// extern (C) void _d_cover_register2(string filename, size_t[] valid, uint[] data, ubyte minPercent)
// as defined in druntime/rt/cover.d.
if (global.params.cov) {
llvm::StringRef fname("_d_cover_register2");

LLType* params[] = {
stringTy,
rt_array(sizeTy),
rt_array(intTy),
byteTy
};

LLFunctionType* fty = LLFunctionType::get(voidTy, params, false);
llvm::Function* fn = LLFunction::Create(fty, LLGlobalValue::ExternalLinkage, fname, M);
fn->setCallingConv(gABI->callingConv(LINKc));
}
}
Loading

0 comments on commit b1ff093

Please sign in to comment.