From 505f18ca2a81a4bb598b4bd620284837aa4a933b Mon Sep 17 00:00:00 2001 From: Johan Engelen Date: Thu, 26 Mar 2015 22:48:53 +0100 Subject: [PATCH] Add coverage analysis ("-cov=...") to ldc2 --- dmd2/mars.h | 5 +- dmd2/module.c | 2 + dmd2/module.h | 5 ++ driver/cl_options.cpp | 34 ++++++++++++ driver/ldmd.cpp | 16 +++--- driver/main.cpp | 2 + gen/coverage.cpp | 51 +++++++++++++++++ gen/coverage.h | 22 ++++++++ gen/module.cpp | 124 ++++++++++++++++++++++++++++++++++++++++++ gen/runtime.cpp | 17 ++++++ gen/statements.cpp | 28 ++++++++++ gen/toir.cpp | 3 + 12 files changed, 300 insertions(+), 9 deletions(-) create mode 100644 gen/coverage.cpp create mode 100644 gen/coverage.h diff --git a/dmd2/mars.h b/dmd2/mars.h index aaecf257b57..6b5044e9f46 100644 --- a/dmd2/mars.h +++ b/dmd2/mars.h @@ -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; @@ -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 diff --git a/dmd2/module.c b/dmd2/module.c index c7dce9348b4..a6de5e3bd73 100644 --- a/dmd2/module.c +++ b/dmd2/module.c @@ -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 } diff --git a/dmd2/module.h b/dmd2/module.h index 60f46fbb0e4..35a2718ff32 100644 --- a/dmd2/module.h +++ b/dmd2/module.h @@ -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 d_cover_valid_init; // initializer for _d_cover_valid #endif Module *isModule() { return this; } diff --git a/driver/cl_options.cpp b/driver/cl_options.cpp index 65827f8da5f..ab7a023e3f6 100644 --- a/driver/cl_options.cpp +++ b/driver/cl_options.cpp @@ -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 { +#if LDC_LLVM_VER >= 307 + CoverageParser(cl::Option &O) : cl::parser(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 fileList( cl::Positional, cl::desc("files")); @@ -412,6 +440,12 @@ cl::opt color("color", cl::desc("Force colored console output"), cl::location(global.params.color)); +cl::opt 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" diff --git a/driver/ldmd.cpp b/driver/ldmd.cpp index f07d2dc2cc9..d5a765b6bb1 100644 --- a/driver/ldmd.cpp +++ b/driver/ldmd.cpp @@ -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\ @@ -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 @@ -935,7 +937,7 @@ void buildCommandLine(std::vector& 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."); diff --git a/driver/main.cpp b/driver/main.cpp index fbc2c09887d..a4c9e54a669 100644 --- a/driver/main.cpp +++ b/driver/main.cpp @@ -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; diff --git a/gen/coverage.cpp b/gen/coverage.cpp new file mode 100644 index 00000000000..133da00f60c --- /dev/null +++ b/gen/coverage.cpp @@ -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); + } + } +} \ No newline at end of file diff --git a/gen/coverage.h b/gen/coverage.h new file mode 100644 index 00000000000..7e9cc88f8a5 --- /dev/null +++ b/gen/coverage.h @@ -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 diff --git a/gen/module.cpp b/gen/module.cpp index 66fc48d0a80..12a8ce0f345 100644 --- a/gen/module.cpp +++ b/gen/module.cpp @@ -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(), 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 arrayInits(array_size); + for (size_t i=0; id_cover_valid_init[i]); + } + m->d_cover_valid->setInitializer(llvm::ConstantArray::get(type, arrayInits)); +} + static void codegenModule(Module* m) { // debug info @@ -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) diff --git a/gen/runtime.cpp b/gen/runtime.cpp index 682deddcd02..2dbc5061bec 100644 --- a/gen/runtime.cpp +++ b/gen/runtime.cpp @@ -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)); + } } diff --git a/gen/statements.cpp b/gen/statements.cpp index 455b217b572..ccc8215693b 100644 --- a/gen/statements.cpp +++ b/gen/statements.cpp @@ -14,6 +14,7 @@ #include "port.h" #include "gen/abi.h" #include "gen/arrays.h" +#include "gen/coverage.h" #include "gen/dvalue.h" #include "gen/irstate.h" #include "gen/llvm.h" @@ -102,6 +103,7 @@ static LLValue* call_string_switch_runtime(llvm::Value* table, Expression* e) return call.getInstruction(); } + ////////////////////////////////////////////////////////////////////////////// /* A visitor to walk entire tree of statements. @@ -372,6 +374,8 @@ class ToIRVisitor : public Visitor { // emit dwarf stop point gIR->DBuilder.EmitStopPoint(stmt->loc.linnum); + emitCoverageLinecountInc(stmt->loc); + // is there a return value expression? if (stmt->exp || (!stmt->exp && (irs->topfunc() == irs->mainFunc)) ) { @@ -491,6 +495,8 @@ class ToIRVisitor : public Visitor { // emit dwarf stop point gIR->DBuilder.EmitStopPoint(stmt->loc.linnum); + emitCoverageLinecountInc(stmt->loc); + if (stmt->exp) { elem* e; // a cast(void) around the expression is allowed, but doesn't require any code @@ -516,6 +522,8 @@ class ToIRVisitor : public Visitor { // start a dwarf lexical block gIR->DBuilder.EmitBlockStart(stmt->loc); + emitCoverageLinecountInc(stmt->loc); + if (stmt->match) DtoRawVarDeclaration(stmt->match); @@ -626,6 +634,7 @@ class ToIRVisitor : public Visitor { gIR->scope() = IRScope(whilebb, endbb); // create the condition + emitCoverageLinecountInc(stmt->condition->loc); DValue* cond_e = toElemDtor(stmt->condition); LLValue* cond_val = DtoCast(stmt->loc, cond_e, Type::tbool)->getRVal(); delete cond_e; @@ -686,6 +695,7 @@ class ToIRVisitor : public Visitor { gIR->scope() = IRScope(condbb,endbb); // create the condition + emitCoverageLinecountInc(stmt->condition->loc); DValue* cond_e = toElemDtor(stmt->condition); LLValue* cond_val = DtoCast(stmt->loc, cond_e, Type::tbool)->getRVal(); delete cond_e; @@ -742,6 +752,7 @@ class ToIRVisitor : public Visitor { llvm::Value* cond_val; if (stmt->condition) { + emitCoverageLinecountInc(stmt->condition->loc); DValue* cond_e = toElemDtor(stmt->condition); cond_val = DtoCast(stmt->loc, cond_e, Type::tbool)->getRVal(); delete cond_e; @@ -769,6 +780,7 @@ class ToIRVisitor : public Visitor { // increment if (stmt->increment) { + emitCoverageLinecountInc(stmt->increment->loc); DValue* inc = toElemDtor(stmt->increment); delete inc; } @@ -800,6 +812,8 @@ class ToIRVisitor : public Visitor { // emit dwarf stop point gIR->DBuilder.EmitStopPoint(stmt->loc.linnum); + emitCoverageLinecountInc(stmt->loc); + if (stmt->ident != 0) { IF_LOG Logger::println("ident = %s", stmt->ident->toChars()); @@ -861,6 +875,8 @@ class ToIRVisitor : public Visitor { // emit dwarf stop point gIR->DBuilder.EmitStopPoint(stmt->loc.linnum); + emitCoverageLinecountInc(stmt->loc); + if (stmt->ident != 0) { IF_LOG Logger::println("ident = %s", stmt->ident->toChars()); @@ -1076,6 +1092,8 @@ class ToIRVisitor : public Visitor { // emit dwarf stop point gIR->DBuilder.EmitStopPoint(stmt->loc.linnum); + emitCoverageLinecountInc(stmt->loc); + assert(stmt->exp); DValue* e = toElemDtor(stmt->exp); @@ -1103,6 +1121,8 @@ class ToIRVisitor : public Visitor { // emit dwarf stop point gIR->DBuilder.EmitStopPoint(stmt->loc.linnum); + emitCoverageLinecountInc(stmt->loc); + llvm::BasicBlock* oldbb = gIR->scopebb(); llvm::BasicBlock* oldend = gIR->scopeend(); @@ -1283,6 +1303,7 @@ class ToIRVisitor : public Visitor { assert(stmt->statement); gIR->DBuilder.EmitBlockStart(stmt->statement->loc); + emitCoverageLinecountInc(stmt->loc); stmt->statement->accept(this); gIR->DBuilder.EmitBlockEnd(); } @@ -1310,6 +1331,7 @@ class ToIRVisitor : public Visitor { assert(stmt->statement); gIR->DBuilder.EmitBlockStart(stmt->statement->loc); + emitCoverageLinecountInc(stmt->loc); stmt->statement->accept(this); gIR->DBuilder.EmitBlockEnd(); } @@ -1671,6 +1693,8 @@ class ToIRVisitor : public Visitor { gIR->DBuilder.EmitStopPoint(stmt->loc.linnum); + emitCoverageLinecountInc(stmt->loc); + llvm::BasicBlock* oldend = gIR->scopeend(); llvm::BasicBlock* bb = llvm::BasicBlock::Create(gIR->context(), "aftergoto", irs->topfunc(), oldend); @@ -1687,6 +1711,8 @@ class ToIRVisitor : public Visitor { gIR->DBuilder.EmitStopPoint(stmt->loc.linnum); + emitCoverageLinecountInc(stmt->loc); + llvm::BasicBlock* oldend = gIR->scopeend(); llvm::BasicBlock* bb = llvm::BasicBlock::Create(gIR->context(), "aftergotodefault", irs->topfunc(), oldend); @@ -1707,6 +1733,8 @@ class ToIRVisitor : public Visitor { gIR->DBuilder.EmitStopPoint(stmt->loc.linnum); + emitCoverageLinecountInc(stmt->loc); + llvm::BasicBlock* oldend = gIR->scopeend(); llvm::BasicBlock* bb = llvm::BasicBlock::Create(gIR->context(), "aftergotocase", irs->topfunc(), oldend); diff --git a/gen/toir.cpp b/gen/toir.cpp index 10bf2d8d08a..ccc4fc53c53 100644 --- a/gen/toir.cpp +++ b/gen/toir.cpp @@ -22,6 +22,7 @@ #include "gen/arrays.h" #include "gen/classes.h" #include "gen/complex.h" +#include "gen/coverage.h" #include "gen/dvalue.h" #include "gen/functions.h" #include "gen/irstate.h" @@ -2125,6 +2126,7 @@ class ToElemVisitor : public Visitor llvm::BranchInst::Create(andand, andandend, ubool, p->scopebb()); p->scope() = IRScope(andand, andandend); + emitCoverageLinecountInc(e->e2->loc); DValue* v = toElemDtor(e->e2); LLValue* vbool = 0; @@ -2172,6 +2174,7 @@ class ToElemVisitor : public Visitor llvm::BranchInst::Create(ororend,oror,ubool,p->scopebb()); p->scope() = IRScope(oror, ororend); + emitCoverageLinecountInc(e->e2->loc); DValue* v = toElemDtor(e->e2); LLValue* vbool = 0;