Index: clang/include/clang/Basic/CodeGenOptions.def =================================================================== --- clang/include/clang/Basic/CodeGenOptions.def +++ clang/include/clang/Basic/CodeGenOptions.def @@ -222,6 +222,8 @@ ///< enable code coverage analysis. CODEGENOPT(DumpCoverageMapping , 1, 0) ///< Dump the generated coverage mapping ///< regions. +CODEGENOPT(MCDCCoverage , 1, 0) ///< Enable MC/DC code coverage criteria. +CODEGENOPT(MCDCMaxConditionCount , 8, 6) ///< MC/DC Maximum Condition Count. /// If -fpcc-struct-return or -freg-struct-return is specified. ENUM_CODEGENOPT(StructReturnConvention, StructReturnConventionKind, 2, SRCK_Default) Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -1297,6 +1297,10 @@ CodeGenOpts<"CoverageMapping">, DefaultFalse, PosFlag, NegFlag, BothFlags<[CoreOption]>>; +defm mcdc_coverage : BoolFOption<"mcdc", + CodeGenOpts<"MCDCCoverage">, DefaultFalse, + PosFlag, + NegFlag, BothFlags<[CoreOption]>>; def fprofile_generate : Flag<["-"], "fprofile-generate">, Group, Flags<[CoreOption]>, HelpText<"Generate instrumented code to collect execution counts into default.profraw (overridden by LLVM_PROFILE_FILE env var)">; Index: clang/lib/CodeGen/CGExprScalar.cpp =================================================================== --- clang/lib/CodeGen/CGExprScalar.cpp +++ clang/lib/CodeGen/CGExprScalar.cpp @@ -4519,6 +4519,11 @@ if (LHSCondVal) { // If we have 1 && X, just emit X. CGF.incrementProfileCounter(E); + // If the top of the logical operator nest, reset the MCDC temp to 0. + if (CGF.MCDCLogOpStack.empty()) + CGF.resetMCDCTempValue(E); + CGF.MCDCLogOpStack.push_back(E); + Value *RHSCond = CGF.EvaluateExprAsBool(E->getRHS()); // If we're generating for profiling or coverage, generate a branch to a @@ -4527,6 +4532,7 @@ // "FalseBlock" after the increment is done. if (InstrumentRegions && CodeGenFunction::isInstrumentedCondition(E->getRHS())) { + CGF.updateMCDCTempValue(E->getRHS(), RHSCond); llvm::BasicBlock *FBlock = CGF.createBasicBlock("land.end"); llvm::BasicBlock *RHSBlockCnt = CGF.createBasicBlock("land.rhscnt"); Builder.CreateCondBr(RHSCond, RHSBlockCnt, FBlock); @@ -4536,6 +4542,11 @@ CGF.EmitBlock(FBlock); } + CGF.MCDCLogOpStack.pop_back(); + // If the top of the logical operator nest, update the MCDC bitmask. + if (CGF.MCDCLogOpStack.empty()) + CGF.updateMCDCBitmask(E); + // ZExt result to int or bool. return Builder.CreateZExtOrBitCast(RHSCond, ResTy, "land.ext"); } @@ -4545,6 +4556,12 @@ return llvm::Constant::getNullValue(ResTy); } + // If the top of the logical operator nest, reset the MCDC temp to 0. + if (CGF.MCDCLogOpStack.empty()) + CGF.resetMCDCTempValue(E); + + CGF.MCDCLogOpStack.push_back(E); + llvm::BasicBlock *ContBlock = CGF.createBasicBlock("land.end"); llvm::BasicBlock *RHSBlock = CGF.createBasicBlock("land.rhs"); @@ -4577,6 +4594,7 @@ // condition coverage. if (InstrumentRegions && CodeGenFunction::isInstrumentedCondition(E->getRHS())) { + CGF.updateMCDCTempValue(E->getRHS(), RHSCond); llvm::BasicBlock *RHSBlockCnt = CGF.createBasicBlock("land.rhscnt"); Builder.CreateCondBr(RHSCond, RHSBlockCnt, ContBlock); CGF.EmitBlock(RHSBlockCnt); @@ -4594,6 +4612,11 @@ // Insert an entry into the phi node for the edge with the value of RHSCond. PN->addIncoming(RHSCond, RHSBlock); + CGF.MCDCLogOpStack.pop_back(); + // If the top of the logical operator nest, update the MCDC bitmask. + if (CGF.MCDCLogOpStack.empty()) + CGF.updateMCDCBitmask(E); + // Artificial location to preserve the scope information { auto NL = ApplyDebugLocation::CreateArtificial(CGF); @@ -4635,6 +4658,11 @@ if (!LHSCondVal) { // If we have 0 || X, just emit X. CGF.incrementProfileCounter(E); + // If the top of the logical operator nest, reset the MCDC temp to 0. + if (CGF.MCDCLogOpStack.empty()) + CGF.resetMCDCTempValue(E); + CGF.MCDCLogOpStack.push_back(E); + Value *RHSCond = CGF.EvaluateExprAsBool(E->getRHS()); // If we're generating for profiling or coverage, generate a branch to a @@ -4643,6 +4671,7 @@ // "FalseBlock" after the increment is done. if (InstrumentRegions && CodeGenFunction::isInstrumentedCondition(E->getRHS())) { + CGF.updateMCDCTempValue(E->getRHS(), RHSCond); llvm::BasicBlock *FBlock = CGF.createBasicBlock("lor.end"); llvm::BasicBlock *RHSBlockCnt = CGF.createBasicBlock("lor.rhscnt"); Builder.CreateCondBr(RHSCond, FBlock, RHSBlockCnt); @@ -4652,6 +4681,11 @@ CGF.EmitBlock(FBlock); } + CGF.MCDCLogOpStack.pop_back(); + // If the top of the logical operator nest, update the MCDC bitmask. + if (CGF.MCDCLogOpStack.empty()) + CGF.updateMCDCBitmask(E); + // ZExt result to int or bool. return Builder.CreateZExtOrBitCast(RHSCond, ResTy, "lor.ext"); } @@ -4661,6 +4695,12 @@ return llvm::ConstantInt::get(ResTy, 1); } + // If the top of the logical operator nest, reset the MCDC temp to 0. + if (CGF.MCDCLogOpStack.empty()) + CGF.resetMCDCTempValue(E); + + CGF.MCDCLogOpStack.push_back(E); + llvm::BasicBlock *ContBlock = CGF.createBasicBlock("lor.end"); llvm::BasicBlock *RHSBlock = CGF.createBasicBlock("lor.rhs"); @@ -4697,6 +4737,7 @@ // condition coverage. if (InstrumentRegions && CodeGenFunction::isInstrumentedCondition(E->getRHS())) { + CGF.updateMCDCTempValue(E->getRHS(), RHSCond); llvm::BasicBlock *RHSBlockCnt = CGF.createBasicBlock("lor.rhscnt"); Builder.CreateCondBr(RHSCond, ContBlock, RHSBlockCnt); CGF.EmitBlock(RHSBlockCnt); @@ -4710,6 +4751,11 @@ CGF.EmitBlock(ContBlock); PN->addIncoming(RHSCond, RHSBlock); + CGF.MCDCLogOpStack.pop_back(); + // If the top of the logical operator nest, update the MCDC bitmask. + if (CGF.MCDCLogOpStack.empty()) + CGF.updateMCDCBitmask(E); + // ZExt result to int. return Builder.CreateZExtOrBitCast(PN, ResTy, "lor.ext"); } @@ -4854,6 +4900,10 @@ return Builder.CreateSelect(CondV, LHS, RHS, "cond"); } + // If the top of the logical operator nest, reset the MCDC temp to 0. + if (CGF.MCDCLogOpStack.empty()) + CGF.resetMCDCTempValue(condExpr); + llvm::BasicBlock *LHSBlock = CGF.createBasicBlock("cond.true"); llvm::BasicBlock *RHSBlock = CGF.createBasicBlock("cond.false"); llvm::BasicBlock *ContBlock = CGF.createBasicBlock("cond.end"); @@ -4889,6 +4939,11 @@ llvm::PHINode *PN = Builder.CreatePHI(LHS->getType(), 2, "cond"); PN->addIncoming(LHS, LHSBlock); PN->addIncoming(RHS, RHSBlock); + + // If the top of the logical operator nest, update the MCDC bitmask. + if (CGF.MCDCLogOpStack.empty()) + CGF.updateMCDCBitmask(condExpr); + return PN; } Index: clang/lib/CodeGen/CGStmt.cpp =================================================================== --- clang/lib/CodeGen/CGStmt.cpp +++ clang/lib/CodeGen/CGStmt.cpp @@ -828,7 +828,19 @@ if (!ThenCount && !getCurrentProfileCount() && CGM.getCodeGenOpts().OptimizationLevel) LH = Stmt::getLikelihood(S.getThen(), S.getElse()); - EmitBranchOnBoolExpr(S.getCond(), ThenBlock, ElseBlock, ThenCount, LH); + + // When measuring MC/DC, always fully evaluate the condition up front using + // EvaluateExprAsBool() so that the coverage bitmask can be updated prior to + // executing the body of the if.then or if.else. This is useful for when + // there is a 'return' within the body, but this is particularly beneficial + // when one if-stmt is nested within another if-stmt so that all of the MC/DC + // updates are kept linear and consistent. + if (!CGM.getCodeGenOpts().MCDCCoverage) + EmitBranchOnBoolExpr(S.getCond(), ThenBlock, ElseBlock, ThenCount, LH); + else { + llvm::Value *BoolCondVal = EvaluateExprAsBool(S.getCond()); + Builder.CreateCondBr(BoolCondVal, ThenBlock, ElseBlock); + } // Emit the 'then' code. EmitBlock(ThenBlock); Index: clang/lib/CodeGen/CodeGenFunction.h =================================================================== --- clang/lib/CodeGen/CodeGenFunction.h +++ clang/lib/CodeGen/CodeGenFunction.h @@ -286,6 +286,9 @@ /// nest would extend. SmallVector OMPLoopNestStack; + /// Stack to track the Logical Operator recursion nest for MC/DC. + SmallVector MCDCLogOpStack; + /// Number of nested loop to be consumed by the last surrounding /// loop-associated directive. int ExpectedOMPLoopDepth = 0; @@ -1515,6 +1518,9 @@ CodeGenPGO PGO; + /// Used by MC/DC to track the execution state of a boolean expression. + Address MCDCTempAddr = Address::invalid(); + /// Calculate branch weights appropriate for PGO data llvm::MDNode *createProfileWeights(uint64_t TrueCount, uint64_t FalseCount) const; @@ -1533,6 +1539,52 @@ PGO.setCurrentStmt(S); } + bool isMCDCCoverageEnabled() { + return (CGM.getCodeGenOpts().hasProfileClangInstr() && + CGM.getCodeGenOpts().MCDCCoverage && + !CurFn->hasFnAttribute(llvm::Attribute::NoProfile)); + } + + /// Allocate a temp value on the stack that MCDC can use to track condition + /// results. + void createMCDCTempValue() { + if (isMCDCCoverageEnabled()) { + PGO.emitMCDCParameters(Builder); + MCDCTempAddr = CreateIRTemp(getContext().UnsignedIntTy, "mcdc.addr"); + return; + } + } + + bool isBinaryLogicalOp(const Expr *S) { + const BinaryOperator *BOp = dyn_cast(S->IgnoreParens()); + return (BOp && BOp->isLogicalOp()); + } + + /// Zero-init the MCDC temp value. + void resetMCDCTempValue(const Expr *S) { + if (isMCDCCoverageEnabled() && isBinaryLogicalOp(S)) { + PGO.emitMCDCTempReset(Builder, S, MCDCTempAddr); + PGO.setCurrentStmt(S); + } + } + + /// Increment the profiler's counter for the given statement by \p StepV. + /// If \p StepV is null, the default increment is 1. + void updateMCDCBitmask(const Expr *S) { + if (isMCDCCoverageEnabled() && isBinaryLogicalOp(S)) { + PGO.emitMCDCBitmaskUpdate(Builder, S, MCDCTempAddr); + PGO.setCurrentStmt(S); + } + } + + /// Update the MCDC temp value with the condition's evaluated result. + void updateMCDCTempValue(const Expr *S, llvm::Value *Val) { + if (isMCDCCoverageEnabled()) { + PGO.emitMCDCTempUpdate(Builder, S, MCDCTempAddr, Val); + PGO.setCurrentStmt(S); + } + } + /// Get the profiler's count for the given statement. uint64_t getProfileCount(const Stmt *S) { return PGO.getStmtCount(S).value_or(0); @@ -4572,7 +4624,8 @@ /// evaluate to true based on PGO data. void EmitBranchOnBoolExpr(const Expr *Cond, llvm::BasicBlock *TrueBlock, llvm::BasicBlock *FalseBlock, uint64_t TrueCount, - Stmt::Likelihood LH = Stmt::LH_None); + Stmt::Likelihood LH = Stmt::LH_None, + const Expr *ConditionalOp = nullptr); /// Given an assignment `*LHS = RHS`, emit a test that checks if \p RHS is /// nonnull, if \p LHS is marked _Nonnull. Index: clang/lib/CodeGen/CodeGenFunction.cpp =================================================================== --- clang/lib/CodeGen/CodeGenFunction.cpp +++ clang/lib/CodeGen/CodeGenFunction.cpp @@ -1235,6 +1235,7 @@ void CodeGenFunction::EmitFunctionBody(const Stmt *Body) { incrementProfileCounter(Body); + createMCDCTempValue(); if (const CompoundStmt *S = dyn_cast(Body)) EmitCompoundStmtWithoutScope(*S); else @@ -1569,6 +1570,13 @@ bool CodeGenFunction::ConstantFoldsToSimpleInteger(const Expr *Cond, bool &ResultBool, bool AllowLabels) { + // If MC/DC is enabled, disable folding so that we can instrument all + // conditions to yield complete test vectors. We still keep track of + // folded conditions during region mapping and visualization. + if (CGM.getCodeGenOpts().hasProfileClangInstr() && + CGM.getCodeGenOpts().MCDCCoverage) + return false; + llvm::APSInt ResultInt; if (!ConstantFoldsToSimpleInteger(Cond, ResultInt, AllowLabels)) return false; @@ -1685,17 +1693,16 @@ /// statement) to the specified blocks. Based on the condition, this might try /// to simplify the codegen of the conditional based on the branch. /// \param LH The value of the likelihood attribute on the True branch. -void CodeGenFunction::EmitBranchOnBoolExpr(const Expr *Cond, - llvm::BasicBlock *TrueBlock, - llvm::BasicBlock *FalseBlock, - uint64_t TrueCount, - Stmt::Likelihood LH) { +void CodeGenFunction::EmitBranchOnBoolExpr( + const Expr *Cond, llvm::BasicBlock *TrueBlock, llvm::BasicBlock *FalseBlock, + uint64_t TrueCount, Stmt::Likelihood LH, const Expr *ConditionalOp) { Cond = Cond->IgnoreParens(); if (const BinaryOperator *CondBOp = dyn_cast(Cond)) { - // Handle X && Y in a condition. if (CondBOp->getOpcode() == BO_LAnd) { + MCDCLogOpStack.push_back(CondBOp); + // If we have "1 && X", simplify the code. "0 && X" would have constant // folded if the case was simple enough. bool ConstantBool = false; @@ -1703,8 +1710,10 @@ ConstantBool) { // br(1 && X) -> br(X). incrementProfileCounter(CondBOp); - return EmitBranchToCounterBlock(CondBOp->getRHS(), BO_LAnd, TrueBlock, - FalseBlock, TrueCount, LH); + EmitBranchToCounterBlock(CondBOp->getRHS(), BO_LAnd, TrueBlock, + FalseBlock, TrueCount, LH); + MCDCLogOpStack.pop_back(); + return; } // If we have "X && 1", simplify the code to use an uncond branch. @@ -1712,8 +1721,10 @@ if (ConstantFoldsToSimpleInteger(CondBOp->getRHS(), ConstantBool) && ConstantBool) { // br(X && 1) -> br(X). - return EmitBranchToCounterBlock(CondBOp->getLHS(), BO_LAnd, TrueBlock, - FalseBlock, TrueCount, LH, CondBOp); + EmitBranchToCounterBlock(CondBOp->getLHS(), BO_LAnd, TrueBlock, + FalseBlock, TrueCount, LH, CondBOp); + MCDCLogOpStack.pop_back(); + return; } // Emit the LHS as a conditional. If the LHS conditional is false, we @@ -1742,11 +1753,13 @@ EmitBranchToCounterBlock(CondBOp->getRHS(), BO_LAnd, TrueBlock, FalseBlock, TrueCount, LH); eval.end(*this); - + MCDCLogOpStack.pop_back(); return; } if (CondBOp->getOpcode() == BO_LOr) { + MCDCLogOpStack.push_back(CondBOp); + // If we have "0 || X", simplify the code. "1 || X" would have constant // folded if the case was simple enough. bool ConstantBool = false; @@ -1754,8 +1767,10 @@ !ConstantBool) { // br(0 || X) -> br(X). incrementProfileCounter(CondBOp); - return EmitBranchToCounterBlock(CondBOp->getRHS(), BO_LOr, TrueBlock, - FalseBlock, TrueCount, LH); + EmitBranchToCounterBlock(CondBOp->getRHS(), BO_LOr, TrueBlock, + FalseBlock, TrueCount, LH); + MCDCLogOpStack.pop_back(); + return; } // If we have "X || 0", simplify the code to use an uncond branch. @@ -1763,10 +1778,11 @@ if (ConstantFoldsToSimpleInteger(CondBOp->getRHS(), ConstantBool) && !ConstantBool) { // br(X || 0) -> br(X). - return EmitBranchToCounterBlock(CondBOp->getLHS(), BO_LOr, TrueBlock, - FalseBlock, TrueCount, LH, CondBOp); + EmitBranchToCounterBlock(CondBOp->getLHS(), BO_LOr, TrueBlock, + FalseBlock, TrueCount, LH, CondBOp); + MCDCLogOpStack.pop_back(); + return; } - // Emit the LHS as a conditional. If the LHS conditional is true, we // want to jump to the TrueBlock. llvm::BasicBlock *LHSFalse = createBasicBlock("lor.lhs.false"); @@ -1797,7 +1813,7 @@ RHSCount, LH); eval.end(*this); - + MCDCLogOpStack.pop_back(); return; } } @@ -1844,14 +1860,14 @@ { ApplyDebugLocation DL(*this, Cond); EmitBranchOnBoolExpr(CondOp->getLHS(), TrueBlock, FalseBlock, - LHSScaledTrueCount, LH); + LHSScaledTrueCount, LH, CondOp); } cond.end(*this); cond.begin(*this); EmitBlock(RHSBlock); EmitBranchOnBoolExpr(CondOp->getRHS(), TrueBlock, FalseBlock, - TrueCount - LHSScaledTrueCount, LH); + TrueCount - LHSScaledTrueCount, LH, CondOp); cond.end(*this); return; @@ -1874,6 +1890,20 @@ CondV = EvaluateExprAsBool(Cond); } + // If not at the top of the logical operator nest, update MCDC temp with the + // boolean result of the evaluated condition. + if (!MCDCLogOpStack.empty()) { + const Expr *MCDCBaseExpr = Cond; + // When a nested ConditionalOperator (ternary) is encountered in a boolean + // expression, MC/DC tracks the result of the ternary, and this is tied to + // the ConditionalOperator expression and not the ternary's LHS or RHS. If + // this is the case, the ConditionalOperator expression is passed through + // the ConditionalOp parameter and then used as the MCDC base expression. + if (ConditionalOp) + MCDCBaseExpr = ConditionalOp; + updateMCDCTempValue(MCDCBaseExpr, CondV); + } + llvm::MDNode *Weights = nullptr; llvm::MDNode *Unpredictable = nullptr; Index: clang/lib/CodeGen/CodeGenPGO.h =================================================================== --- clang/lib/CodeGen/CodeGenPGO.h +++ clang/lib/CodeGen/CodeGenPGO.h @@ -32,8 +32,11 @@ std::array NumValueSites; unsigned NumRegionCounters; + unsigned MCDCBitmaskBytes; uint64_t FunctionHash; std::unique_ptr> RegionCounterMap; + std::unique_ptr> RegionBitmaskMap; + std::unique_ptr> RegionCondIDMap; std::unique_ptr> StmtCountMap; std::unique_ptr ProfRecord; std::vector RegionCounts; @@ -42,7 +45,8 @@ public: CodeGenPGO(CodeGenModule &CGModule) : CGM(CGModule), FuncNameVar(nullptr), NumValueSites({{0}}), - NumRegionCounters(0), FunctionHash(0), CurrentRegionCount(0) {} + NumRegionCounters(0), MCDCBitmaskBytes(0), FunctionHash(0), + CurrentRegionCount(0) {} /// Whether or not we have PGO region data for the current function. This is /// false both when we have no data at all and when our data has been @@ -102,10 +106,17 @@ bool IsInMainFile); bool skipRegionMappingForDecl(const Decl *D); void emitCounterRegionMapping(const Decl *D); + bool canEmitMCDCCoverage(const CGBuilderTy &Builder); public: void emitCounterIncrement(CGBuilderTy &Builder, const Stmt *S, llvm::Value *StepV); + void emitMCDCBitmaskUpdate(CGBuilderTy &Builder, const Expr *S, + Address TempAddr); + void emitMCDCParameters(CGBuilderTy &Builder); + void emitMCDCTempReset(CGBuilderTy &Builder, const Expr *S, Address TempAddr); + void emitMCDCTempUpdate(CGBuilderTy &Builder, const Expr *S, Address TempAddr, + llvm::Value *Val); /// Return the region count for the counter at the given index. uint64_t getRegionCount(const Stmt *S) { Index: clang/lib/CodeGen/CodeGenPGO.cpp =================================================================== --- clang/lib/CodeGen/CodeGenPGO.cpp +++ clang/lib/CodeGen/CodeGenPGO.cpp @@ -160,13 +160,26 @@ PGOHash Hash; /// The map of statements to counters. llvm::DenseMap &CounterMap; + /// The next bitmask byte index to assign. + unsigned NextBitmask; + /// The map of statements to bitmask coverage objects. + llvm::DenseMap &BitmaskMap; + /// Maximum number of supported MC/DC conditions in a boolean expression. + unsigned MaxCond; /// The profile version. uint64_t ProfileVersion; + /// Diagnostics Engine used to report warnings. + DiagnosticsEngine &Diag; + /// Whether counters are limited only to functions. + bool FunctionCoverageOnly; MapRegionCounters(PGOHashVersion HashVersion, uint64_t ProfileVersion, - llvm::DenseMap &CounterMap) + llvm::DenseMap &CounterMap, + llvm::DenseMap &BitmaskMap, + unsigned MaxCond, DiagnosticsEngine &Diag) : NextCounter(0), Hash(HashVersion), CounterMap(CounterMap), - ProfileVersion(ProfileVersion) {} + NextBitmask(0), BitmaskMap(BitmaskMap), MaxCond(MaxCond), + ProfileVersion(ProfileVersion), Diag(Diag) {} // Blocks and lambdas are handled as separate functions, so we need not // traverse them in the parent context. @@ -206,15 +219,128 @@ return Type; } + /// The following stacks are used with dataTraverseStmtPre() and + /// dataTraverseStmtPost() to track the depth of nested logical operators in a + /// boolean expression in a function. The ultimate purpose is to keep track + /// of the number of leaf-level conditions in the boolean expression so that a + /// profile bitmask can be allocated based on that number. + /// + /// The stacks are also used to find error cases and notify the user. A + /// standard logical operator nest for a boolean expression could be in a form + /// similar to this: "x = a && b && c && (d || f)" + unsigned NumCond = 0; + bool SplitNestedLogicalOp = false; + SmallVector NonLogOpStack; + SmallVector LogOpStack; + + // Hook: dataTraverseStmtPre() is invoked prior to visiting an AST Stmt node. + bool dataTraverseStmtPre(Stmt *S) { + /// If MC/DC is not enabled, MaxCond will be set to 0. Don't do anything. + if (MaxCond == 0) + return true; + + /// At the top of the logical operator nest, reset the number of conditions. + if (LogOpStack.empty()) + NumCond = 0; + + if (isa(S)) { + const Expr *E = reinterpret_cast(S); + const BinaryOperator *BinOp = dyn_cast(E->IgnoreParens()); + if (BinOp && BinOp->isLogicalOp()) { + /// Check for "split-nested" logical operators. This happens when a new + /// boolean expression logical-op nest is encountered within an existing + /// boolean expression, separated by a non-logical operator. For + /// example, in "x = (a && b && c && foo(d && f))", the "d && f" case + /// starts a new boolean expression that is separated from the other + /// conditions by the operator foo(). Split-nested cases are not + /// supported by MC/DC. + SplitNestedLogicalOp = SplitNestedLogicalOp || !NonLogOpStack.empty(); + + LogOpStack.push_back(BinOp); + return true; + } + } + + if (!LogOpStack.empty()) { + /// Keep track of non-logical operators. These are OK as long as we don't + /// encounter a new logical operator after seeing one. + NonLogOpStack.push_back(S); + } + + return true; + } + + // Hook: dataTraverseStmtPost() is invoked by the AST visitor after visiting + // an AST Stmt node. MC/DC will use it to to signal when the top of a + // logical operation (boolean expression) nest is encountered. + bool dataTraverseStmtPost(Stmt *S) { + /// If MC/DC is not enabled, MaxCond will be set to 0. Don't do anything. + if (MaxCond == 0) + return true; + + if (isa(S)) { + const Expr *E = reinterpret_cast(S); + const BinaryOperator *BinOp = dyn_cast(E->IgnoreParens()); + if (BinOp && BinOp->isLogicalOp()) { + assert(LogOpStack.back() == BinOp); + LogOpStack.pop_back(); + + /// At the top of logical operator nest: + if (LogOpStack.empty()) { + /// Was the "split-nested" logical operator case encountered? + if (SplitNestedLogicalOp) { + unsigned DiagID = Diag.getCustomDiagID( + DiagnosticsEngine::Error, + "MC/DC: Unsupported boolean expression; " + "contains an operation with a nested boolean expression."); + Diag.Report(S->getBeginLoc(), DiagID); + return false; + + /// Was the maximum number of conditions encountered? + } else if (NumCond > MaxCond) { + unsigned DiagID = Diag.getCustomDiagID( + DiagnosticsEngine::Error, + "MC/DC: Unsupported boolean expression; " + "number of conditions (%0) exceeds max (%1)."); + Diag.Report(S->getBeginLoc(), DiagID) << NumCond << MaxCond; + return false; + + // Otherwise, allocate the number of bytes required for the bitmask + // based on the number of conditions. Must be at least 1-byte long. + } else { + BitmaskMap[BinOp] = NextBitmask; + unsigned SizeInBits = llvm::alignTo(1L << NumCond, CHAR_BIT); + NextBitmask += (SizeInBits / CHAR_BIT); + } + } + return true; + } + } + + if (!LogOpStack.empty()) { + NonLogOpStack.pop_back(); + } + + return true; + } + /// The RHS of all logical operators gets a fresh counter in order to count /// how many times the RHS evaluates to true or false, depending on the /// semantics of the operator. This is only valid for ">= v7" of the profile - /// version so that we facilitate backward compatibility. + /// version so that we facilitate backward compatibility. In addition, in + /// order to use MC/DC, count the number of total LHS and RHS conditions. bool VisitBinaryOperator(BinaryOperator *S) { - if (ProfileVersion >= llvm::IndexedInstrProf::Version7) - if (S->isLogicalOp() && - CodeGenFunction::isInstrumentedCondition(S->getRHS())) - CounterMap[S->getRHS()] = NextCounter++; + if (ProfileVersion >= llvm::IndexedInstrProf::Version7) { + if (S->isLogicalOp()) { + if (CodeGenFunction::isInstrumentedCondition(S->getLHS())) { + NumCond++; + } + if (CodeGenFunction::isInstrumentedCondition(S->getRHS())) { + CounterMap[S->getRHS()] = NextCounter++; + NumCond++; + } + } + } return Base::VisitBinaryOperator(S); } @@ -848,8 +974,17 @@ ProfileVersion = PGOReader->getVersion(); } + // If MC/DC is enabled, set the MaxConditions to the preset value. Otherwise, + // set it to zero. + unsigned MCDCMaxConditions = 0; + if (CGM.getCodeGenOpts().MCDCCoverage) + MCDCMaxConditions = CGM.getCodeGenOpts().MCDCMaxConditionCount; + RegionCounterMap.reset(new llvm::DenseMap); - MapRegionCounters Walker(HashVersion, ProfileVersion, *RegionCounterMap); + RegionBitmaskMap.reset(new llvm::DenseMap); + MapRegionCounters Walker(HashVersion, ProfileVersion, *RegionCounterMap, + *RegionBitmaskMap, MCDCMaxConditions, + CGM.getDiags()); if (const FunctionDecl *FD = dyn_cast_or_null(D)) Walker.TraverseDecl(const_cast(FD)); else if (const ObjCMethodDecl *MD = dyn_cast_or_null(D)) @@ -860,6 +995,7 @@ Walker.TraverseDecl(const_cast(CD)); assert(Walker.NextCounter > 0 && "no entry counter mapped for decl"); NumRegionCounters = Walker.NextCounter; + MCDCBitmaskBytes = Walker.NextBitmask; FunctionHash = Walker.Hash.finalize(); } @@ -891,9 +1027,11 @@ std::string CoverageMapping; llvm::raw_string_ostream OS(CoverageMapping); + RegionCondIDMap.reset(new llvm::DenseMap); CoverageMappingGen MappingGen(*CGM.getCoverageMapping(), CGM.getContext().getSourceManager(), - CGM.getLangOpts(), RegionCounterMap.get()); + CGM.getLangOpts(), RegionCounterMap.get(), + RegionBitmaskMap.get(), RegionCondIDMap.get()); MappingGen.emitCounterMapping(D, OS); OS.flush(); @@ -972,6 +1110,97 @@ makeArrayRef(Args)); } +bool CodeGenPGO::canEmitMCDCCoverage(const CGBuilderTy &Builder) { + return (CGM.getCodeGenOpts().hasProfileClangInstr() && + CGM.getCodeGenOpts().MCDCCoverage && + Builder.GetInsertBlock()); +} + +void CodeGenPGO::emitMCDCParameters(CGBuilderTy &Builder) { + if (!canEmitMCDCCoverage(Builder) || !RegionBitmaskMap) + return; + + auto *I8PtrTy = llvm::Type::getInt8PtrTy(CGM.getLLVMContext()); + + // Emit intrinsic representing MCDC bitmask parameters at function entry. + // This is used by the instrumentation pass, but it isn't actually lowered to + // anything. + llvm::Value *Args[3] = {llvm::ConstantExpr::getBitCast(FuncNameVar, I8PtrTy), + Builder.getInt64(FunctionHash), + Builder.getInt32(MCDCBitmaskBytes)}; + Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::instrprof_mcdc_parameters), Args); +} + +void CodeGenPGO::emitMCDCBitmaskUpdate(CGBuilderTy &Builder, const Expr *S, + Address TempAddr) { + if (!canEmitMCDCCoverage(Builder) || !RegionBitmaskMap) + return; + + S = S->IgnoreParens(); + + if (RegionBitmaskMap->find(S) == RegionBitmaskMap->end()) + return; + + unsigned Bitmask = (*RegionBitmaskMap)[S]; + auto *I8PtrTy = llvm::Type::getInt8PtrTy(CGM.getLLVMContext()); + + // Emit intrinsic responsible for updating the bitmask corresponding to a + // boolean expression. The update is based on the value loaded from a + // pointer to a dedicated temporary value on the stack that is itself updated + // via emitMCDCTempReset() and emitMCDCTempUpdate(). The value represents + // an executed test vector. + llvm::Value *Args[5] = {llvm::ConstantExpr::getBitCast(FuncNameVar, I8PtrTy), + Builder.getInt64(FunctionHash), + Builder.getInt32(MCDCBitmaskBytes), + Builder.getInt32(Bitmask), TempAddr.getPointer()}; + Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::instrprof_mcdc_bitmask_update), Args); +} + +void CodeGenPGO::emitMCDCTempReset(CGBuilderTy &Builder, const Expr *S, + Address TempAddr) { + if (!canEmitMCDCCoverage(Builder) || !RegionBitmaskMap) + return; + + S = S->IgnoreParens(); + + if (RegionBitmaskMap->find(S) == RegionBitmaskMap->end()) + return; + + // Emit intrinsic that resets a dedicated temporary value on the stack to 0. + Builder.CreateStore(Builder.getInt32(0), TempAddr); +} + +void CodeGenPGO::emitMCDCTempUpdate(CGBuilderTy &Builder, const Expr *S, + Address TempAddr, llvm::Value *Val) { + if (!canEmitMCDCCoverage(Builder) || !RegionCondIDMap) + return; + + if (const UnaryOperator *UnOp = dyn_cast(S->IgnoreParens())) + if (UnOp->getOpcode() == UO_LNot) + S = UnOp->getSubExpr(); + S = S->IgnoreParens(); + + if (RegionCondIDMap->find(S) == RegionCondIDMap->end()) + return; + + unsigned CondID = (*RegionCondIDMap)[S]; + assert(CondID > 0 && "Condition has no ID!"); + + auto *I8PtrTy = llvm::Type::getInt8PtrTy(CGM.getLLVMContext()); + + // Emit intrinsic that updates a dedicated temporary value on the stack after + // a condition is evaluated. After the set of conditions has been updated, + // the resulting value is used to update the boolean expression's bitmask. + llvm::Value *Args[5] = {llvm::ConstantExpr::getBitCast(FuncNameVar, I8PtrTy), + Builder.getInt64(FunctionHash), + Builder.getInt32(CondID - 1), TempAddr.getPointer(), + Val}; + Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::instrprof_mcdc_temp_update), Args); +} + void CodeGenPGO::setValueProfilingFlag(llvm::Module &M) { if (CGM.getCodeGenOpts().hasProfileClangInstr()) M.addModuleFlag(llvm::Module::Warning, "EnableValueProfiling", Index: clang/lib/CodeGen/CoverageMappingGen.h =================================================================== --- clang/lib/CodeGen/CoverageMappingGen.h +++ clang/lib/CodeGen/CoverageMappingGen.h @@ -151,16 +151,22 @@ SourceManager &SM; const LangOptions &LangOpts; llvm::DenseMap *CounterMap; + llvm::DenseMap *BitmaskMap; + llvm::DenseMap *CondIDMap; public: CoverageMappingGen(CoverageMappingModuleGen &CVM, SourceManager &SM, const LangOptions &LangOpts) - : CVM(CVM), SM(SM), LangOpts(LangOpts), CounterMap(nullptr) {} + : CVM(CVM), SM(SM), LangOpts(LangOpts), CounterMap(nullptr), + BitmaskMap(nullptr), CondIDMap(nullptr) {} CoverageMappingGen(CoverageMappingModuleGen &CVM, SourceManager &SM, const LangOptions &LangOpts, - llvm::DenseMap *CounterMap) - : CVM(CVM), SM(SM), LangOpts(LangOpts), CounterMap(CounterMap) {} + llvm::DenseMap *CounterMap, + llvm::DenseMap *BitmaskMap, + llvm::DenseMap *CondIDMap) + : CVM(CVM), SM(SM), LangOpts(LangOpts), CounterMap(CounterMap), + BitmaskMap(BitmaskMap), CondIDMap(CondIDMap) {} /// Emit the coverage mapping data which maps the regions of /// code to counters that will be used to find the execution Index: clang/lib/CodeGen/CoverageMappingGen.cpp =================================================================== --- clang/lib/CodeGen/CoverageMappingGen.cpp +++ clang/lib/CodeGen/CoverageMappingGen.cpp @@ -99,6 +99,13 @@ /// Secondary Counter used for Branch Regions for "False" branches. Optional FalseCount; + unsigned Mask = 0; + unsigned Conditions = 0; + + unsigned ID = 0; + unsigned TrueID = 0; + unsigned FalseID = 0; + /// The region's starting location. Optional LocStart; @@ -116,10 +123,18 @@ } SourceMappingRegion(Counter Count, Optional FalseCount, + unsigned ID, unsigned TrueID, unsigned FalseID, Optional LocStart, Optional LocEnd, bool GapRegion = false) - : Count(Count), FalseCount(FalseCount), LocStart(LocStart), - LocEnd(LocEnd), GapRegion(GapRegion) {} + : Count(Count), FalseCount(FalseCount), ID(ID), TrueID(TrueID), + FalseID(FalseID), LocStart(LocStart), LocEnd(LocEnd), + GapRegion(GapRegion) {} + + SourceMappingRegion(unsigned Mask, unsigned Conditions, + Optional LocStart, + Optional LocEnd) + : Mask(Mask), Conditions(Conditions), LocStart(LocStart), LocEnd(LocEnd), + GapRegion(false) {} const Counter &getCounter() const { return Count; } @@ -156,6 +171,13 @@ void setGap(bool Gap) { GapRegion = Gap; } bool isBranch() const { return FalseCount.has_value(); } + unsigned getID() const { return ID; } + unsigned getTrueID() const { return TrueID; } + unsigned getFalseID() const { return FalseID; } + + bool isDecision() const { return Conditions != 0; } + unsigned getMask() const { return Mask; } + unsigned getConditions() const { return Conditions; } }; /// Spelling locations for the start and end of a source region. @@ -444,8 +466,13 @@ SR.LineEnd, SR.ColumnEnd)); } else if (Region.isBranch()) { MappingRegions.push_back(CounterMappingRegion::makeBranchRegion( - Region.getCounter(), Region.getFalseCounter(), *CovFileID, - SR.LineStart, SR.ColumnStart, SR.LineEnd, SR.ColumnEnd)); + Region.getCounter(), Region.getFalseCounter(), Region.getID(), + Region.getTrueID(), Region.getFalseID(), *CovFileID, SR.LineStart, + SR.ColumnStart, SR.LineEnd, SR.ColumnEnd)); + } else if (Region.isDecision()) { + MappingRegions.push_back(CounterMappingRegion::makeDecisionRegion( + Region.getMask(), Region.getConditions(), *CovFileID, SR.LineStart, + SR.ColumnStart, SR.LineEnd, SR.ColumnEnd)); } else { MappingRegions.push_back(CounterMappingRegion::makeRegion( Region.getCounter(), *CovFileID, SR.LineStart, SR.ColumnStart, @@ -532,6 +559,229 @@ } }; +/// A wrapper object for maintaining stacks to track the resursive AST visitor +/// walks for the purpose of assigning IDs to leaf-level conditions measured by +/// MC/DC. The object is created with a reference to the BitmaskMap that was +/// created during the initial AST walk. The presence of a bitmask associated +/// with a boolean expression (top-level logical operator nest) indicates that +/// the boolean expression qualified for MC/DC. The resulting condition IDs +/// are preserved in a map reference that is also provided during object +/// creation. +struct MCDCCoverageBuilder { + + /// The AST walk recursively visits nested logical-AND or logical-OR binary + /// operator nodes and then visits their LHS and RHS children nodes. As this + /// happens, the algorithm will assign IDs to each operator's LHS and RHS side + /// as the walk moves deeper into the nest. At each level of the recursive + /// nest, the LHS and RHS may actually correspond to larger subtrees (not + /// leaf-conditions). If this is the case, when that node is visited, the ID + /// assigned to the subtree is re-assigned to its LHS, and a new ID is given + /// to its RHS. At the end of the walk, all leaf-level conditions will have a + /// unique ID -- keep in mind that the final set of IDs may not be in + /// numerical order from left to right. + /// + /// Example: "x = (A && B) || (C && D) || (D && F)" + /// + /// Visit Depth1: + /// (A && B) || (C && D) || (D && F) + /// ^-------LHS--------^ ^-RHS--^ + /// ID=1 ID=2 + /// + /// Visit LHS-Depth2: + /// (A && B) || (C && D) + /// ^-LHS--^ ^-RHS--^ + /// ID=1 ID=3 + /// + /// Visit LHS-Depth3: + /// (A && B) + /// LHS RHS + /// ID=1 ID=4 + /// + /// Visit RHS-Depth3: + /// (C && D) + /// LHS RHS + /// ID=3 ID=5 + /// + /// Visit RHS-Depth2: (D && F) + /// LHS RHS + /// ID=2 ID=6 + /// + /// Visit Depth1: + /// (A && B) || (C && D) || (D && F) + /// ID=1 ID=4 ID=3 ID=5 ID=2 ID=6 + /// + /// A node ID of '0' always means MC/DC isn't being tracked. + /// + /// As the AST walk proceeds recursively, the algorithm will also use stacks + /// to track the IDs of logical-AND and logical-OR operations on the RHS so + /// that it can be determined which nodes are executed next, depending on how + /// a LHS or RHS of a logical-AND or logical-OR is evaluated. This + /// information relies on the assigned IDs and are embedded within the + /// coverage region IDs of each branch region associated with a leaf-level + /// condition. This information helps the visualization tool reconstruct all + /// possible test vectors for the purposes of MC/DC analysis. if a "next" node + /// ID is '0', it means it's the end of the test vector. The following rules + /// are used: + /// + /// For logical-AND ("LHS && RHS"): + /// - If LHS is TRUE, execution goes to the RHS node. + /// - If LHS is FALSE, execution goes to the LHS node of the next logical-OR. + /// If that does not exist, execution exits (ID == 0). + /// + /// - If RHS is TRUE, execution goes to LHS node of the next logical-AND. + /// If that does not exist, execution exits (ID == 0). + /// - If RHS is FALSE, execution goes to the LHS node of the next logical-OR. + /// If that does not exist, execution exits (ID == 0). + /// + /// For logical-OR ("LHS || RHS"): + /// - If LHS is TRUE, execution goes to the LHS node of the next logical-AND. + /// If that does not exist, execution exits (ID == 0). + /// - If LHS is FALSE, execution goes to the RHS node. + /// + /// - If RHS is TRUE, execution goes to LHS node of the next logical-AND. + /// If that does not exist, execution exits (ID == 0). + /// - If RHS is FALSE, execution goes to the LHS node of the next logical-OR. + /// If that does not exist, execution exits (ID == 0). + /// + /// Finally, the condition IDs are also used when instrumenting the code to + /// indicate a unique offset into a temporary bitmask that represents the true + /// or false evaluation of that particular condition. + +private: + CodeGenModule &CGM; + + std::stack AndRHS; + std::stack OrRHS; + std::stack NestLevel; + llvm::DenseMap &CondIDs; + llvm::DenseMap &BitmaskMap; + unsigned NextID = 1; + bool NotMapped = false; + + /// Is this a logical-AND operation? + bool isLAnd(const BinaryOperator *E) { return E->getOpcode() == BO_LAnd; } + + /// Push an ID onto the corresponding RHS stack. + void pushRHS(const BinaryOperator *E) { + std::stack &rhs = isLAnd(E) ? AndRHS : OrRHS; + rhs.push(CondIDs[stripCond(E->getRHS())]); + } + + /// Pop an ID from the corresponding RHS stack. + void popRHS(const BinaryOperator *E) { + std::stack &rhs = isLAnd(E) ? AndRHS : OrRHS; + if (!rhs.empty()) + rhs.pop(); + } + + /// If the expected ID is on top, pop it off the corresponding RHS stack. + void popRHSifTop(const BinaryOperator *E) { + if (!OrRHS.empty() && CondIDs[(Expr *)E] == OrRHS.top()) + OrRHS.pop(); + else if (!AndRHS.empty() && CondIDs[(Expr *)E] == AndRHS.top()) + AndRHS.pop(); + } + + /// Ignore parentheses and logical-NOT to track conditions consistently. + const Expr *stripCond(const Expr *C) { + if (const UnaryOperator *UnOp = dyn_cast(C->IgnoreParens())) + if (UnOp->getOpcode() == UO_LNot) + C = UnOp->getSubExpr(); + return C->IgnoreParens(); + } + +public: + MCDCCoverageBuilder(CodeGenModule &CGM, + llvm::DenseMap &CondIDMap, + llvm::DenseMap &BitmaskMap) + : CGM(CGM), CondIDs(CondIDMap), BitmaskMap(BitmaskMap) {} + + /// Return the ID of the RHS of the next, upper nest-level logical-OR. + unsigned getNextLOrCondID() { return OrRHS.empty() ? 0 : OrRHS.top(); } + + /// Return the ID of the RHS of the next, upper nest-level logical-AND. + unsigned getNextLAndCondID() { return AndRHS.empty() ? 0 : AndRHS.top(); } + + /// Return the ID of a given condition. + unsigned getCondID(Expr *Cond) { + auto I = CondIDs.find(stripCond(Cond)); + if (I == CondIDs.end()) + return 0; + else + return I->second; + } + + /// Push the binary operator statement to track the nest level and assign IDs + /// to the operator's LHS and RHS. The RHS may be a larger subtree that is + /// broken up on successive levels. + void pushAndAssignIDs(const BinaryOperator *E) { + if (CGM.getCodeGenOpts().MCDCCoverage) { + // If binary expression is disqualified, don't do mapping. + if (NestLevel.empty() && + BitmaskMap.find(stripCond((Expr *)E)) == BitmaskMap.end()) + NotMapped = true; + + // Push Stmt on 'NestLevel' stack to keep track of nest location. + NestLevel.push(E); + + // Don't go any further if we don't need to map condition IDs. + if (NotMapped) + return; + + // If the operator itself has an assigned ID, this means it represents a + // larger subtree. In this case, pop its ID out of the RHS stack and + // assign that ID to its LHS node. Its RHS will receive a new ID. + if (CondIDs.find(stripCond((Expr *)E)) != CondIDs.end()) { + // If Stmt has an ID, assign its ID to LHS + CondIDs[stripCond(E->getLHS())] = CondIDs[(Expr *)E]; + + // Since the operator's LHS assumes the operator's same ID, pop the + // operator from the RHS stack so that if LHS short-circuits, it won't + // be incorrectly re-used as the node executed next. + popRHSifTop(E); + } else { + // Otherwise, assign ID+1 to LHS. + CondIDs[stripCond(E->getLHS())] = NextID++; + } + + // Assign ID+1 to RHS. + CondIDs[stripCond(E->getRHS())] = NextID++; + + // Push ID of Stmt's RHS so that LHS nodes know about it + pushRHS(E); + } + } + + /// Pop the binary operator from the next level. If the walk is at the top of + /// the next, assign the total number of conditions. + unsigned popAndReturnCondCount(const BinaryOperator *E) { + unsigned TotalConds = 0; + if (CGM.getCodeGenOpts().MCDCCoverage) { + // Pop Stmt from 'NestLevel' stack. + assert(NestLevel.top() == E); + NestLevel.pop(); + + // Reset state if not doing mapping. + if (NestLevel.empty() && NotMapped) { + NotMapped = false; + return 0; + } + + // Pop RHS ID. + popRHS(E); + + // If at the parent (NestLevel=0), set conds and reset. + if (NestLevel.empty()) { + TotalConds = NextID - 1; + + // Reset ID back to beginning. + NextID = 1; + } + } + return TotalConds; + } +}; + /// A StmtVisitor that creates coverage mapping regions which map /// from the source code locations to the PGO counters. struct CounterCoverageMappingBuilder @@ -540,9 +790,15 @@ /// The map of statements to count values. llvm::DenseMap &CounterMap; + /// The map of statements to bitmask coverage object values. + llvm::DenseMap &BitmaskMap; + /// A stack of currently live regions. std::vector RegionStack; + /// An object to manage MCDC regions. + MCDCCoverageBuilder MCDCBuilder; + CounterExpressionBuilder Builder; /// A location in the most recently visited file or macro. @@ -579,19 +835,32 @@ return Counter::getCounter(CounterMap[S]); } + unsigned getRegionBitmask(const Stmt *S) { return BitmaskMap[S]; } + /// Push a region onto the stack. /// /// Returns the index on the stack where the region was pushed. This can be /// used with popRegions to exit a "scope", ending the region that was pushed. size_t pushRegion(Counter Count, Optional StartLoc = None, Optional EndLoc = None, - Optional FalseCount = None) { + Optional FalseCount = None, unsigned ID = 0, + unsigned TrueID = 0, unsigned FalseID = 0) { if (StartLoc && !FalseCount) { MostRecentLocation = *StartLoc; } - RegionStack.emplace_back(Count, FalseCount, StartLoc, EndLoc); + RegionStack.emplace_back(Count, FalseCount, ID, TrueID, FalseID, StartLoc, + EndLoc); + + return RegionStack.size() - 1; + } + + size_t pushRegion(unsigned Mask, unsigned Conditions, + Optional StartLoc = None, + Optional EndLoc = None) { + + RegionStack.emplace_back(Mask, Conditions, StartLoc, EndLoc); return RegionStack.size() - 1; } @@ -721,7 +990,9 @@ /// and add it to the function's SourceRegions. A branch region tracks a /// "True" counter and a "False" counter for boolean expressions that /// result in the generation of a branch. - void createBranchRegion(const Expr *C, Counter TrueCnt, Counter FalseCnt) { + void createBranchRegion(const Expr *C, Counter TrueCnt, Counter FalseCnt, + unsigned ID = 0, unsigned TrueID = 0, + unsigned FalseID = 0) { // Check for NULL conditions. if (!C) return; @@ -739,13 +1010,21 @@ // CodeGenFunction.c always returns false, but that is very heavy-handed. if (ConditionFoldsToBool(C)) popRegions(pushRegion(Counter::getZero(), getStart(C), getEnd(C), - Counter::getZero())); + Counter::getZero(), ID, TrueID, FalseID)); else // Otherwise, create a region with the True counter and False counter. - popRegions(pushRegion(TrueCnt, getStart(C), getEnd(C), FalseCnt)); + popRegions(pushRegion(TrueCnt, getStart(C), getEnd(C), FalseCnt, ID, + TrueID, FalseID)); } } + /// Create a Decision Region with a Mask ID and number of Conditions. This + /// type of region "contains" branch regions, one for each of the conditions. + /// The visualization tool will group everything together. + void createDecisionRegion(const Expr *C, unsigned Mask, unsigned Conditions) { + popRegions(pushRegion(Mask, Conditions, getStart(C), getEnd(C))); + } + /// Create a Branch Region around a SwitchCase for code coverage /// and add it to the function's SourceRegions. void createSwitchCaseRegion(const SwitchCase *SC, Counter TrueCnt, @@ -826,8 +1105,9 @@ // we've seen this region. if (StartLocs.insert(Loc).second) { if (I.isBranch()) - SourceRegions.emplace_back(I.getCounter(), I.getFalseCounter(), Loc, - getEndOfFileOrMacro(Loc), I.isBranch()); + SourceRegions.emplace_back( + I.getCounter(), I.getFalseCounter(), I.getID(), I.getTrueID(), + I.getFalseID(), Loc, getEndOfFileOrMacro(Loc), I.isBranch()); else SourceRegions.emplace_back(I.getCounter(), Loc, getEndOfFileOrMacro(Loc)); @@ -946,9 +1226,13 @@ CounterCoverageMappingBuilder( CoverageMappingModuleGen &CVM, - llvm::DenseMap &CounterMap, SourceManager &SM, + llvm::DenseMap &CounterMap, + llvm::DenseMap &BitmaskMap, + llvm::DenseMap &CondIDMap, SourceManager &SM, const LangOptions &LangOpts) - : CoverageMappingBuilder(CVM, SM, LangOpts), CounterMap(CounterMap) {} + : CoverageMappingBuilder(CVM, SM, LangOpts), CounterMap(CounterMap), + BitmaskMap(BitmaskMap), + MCDCBuilder(CVM.getCodeGenModule(), CondIDMap, BitmaskMap) {} /// Write the mapping data to the output stream void write(llvm::raw_ostream &OS) { @@ -1475,6 +1759,9 @@ } void VisitBinLAnd(const BinaryOperator *E) { + // Keep track of Binary Operator and assign MCDC condition IDs + MCDCBuilder.pushAndAssignIDs(E); + extendRegion(E->getLHS()); propagateCounts(getRegion().getCounter(), E->getLHS()); handleFileExit(getEnd(E->getLHS())); @@ -1483,6 +1770,11 @@ extendRegion(E->getRHS()); propagateCounts(getRegionCounter(E), E->getRHS()); + // Process Binary Operator and create MCDC Decision Region if top-level + unsigned NumConds = 0; + if ((NumConds = MCDCBuilder.popAndReturnCondCount(E))) + createDecisionRegion(E, getRegionBitmask(E), NumConds); + // Extract the RHS's Execution Counter. Counter RHSExecCnt = getRegionCounter(E); @@ -1492,16 +1784,36 @@ // Extract the Parent Region Counter. Counter ParentCnt = getRegion().getCounter(); + // Extract the MCDC condition IDs (returns 0 if not needed). + unsigned NextOrID = MCDCBuilder.getNextLOrCondID(); + unsigned NextAndID = MCDCBuilder.getNextLAndCondID(); + unsigned LHSid = MCDCBuilder.getCondID(E->getLHS()); + unsigned RHSid = MCDCBuilder.getCondID(E->getRHS()); + // Create Branch Region around LHS condition. + // MC/DC: For "LHS && RHS" + // - If LHS is TRUE, execution goes to the RHS. + // - If LHS is FALSE, execution goes to the LHS of the next logical-OR. + // If that does not exist, execution exits (ID == 0). createBranchRegion(E->getLHS(), RHSExecCnt, - subtractCounters(ParentCnt, RHSExecCnt)); + subtractCounters(ParentCnt, RHSExecCnt), LHSid, RHSid, + NextOrID); // Create Branch Region around RHS condition. + // MC/DC: For "LHS && RHS" + // - If RHS is TRUE, execution goes to LHS of the next logical-AND. + // If that does not exist, execution exits (ID == 0). + // - If RHS is FALSE, execution goes to the LHS of the next logical-OR. + // If that does not exist, execution exits (ID == 0). createBranchRegion(E->getRHS(), RHSTrueCnt, - subtractCounters(RHSExecCnt, RHSTrueCnt)); + subtractCounters(RHSExecCnt, RHSTrueCnt), RHSid, + NextAndID, NextOrID); } void VisitBinLOr(const BinaryOperator *E) { + // Keep track of Binary Operator and assign MCDC condition IDs + MCDCBuilder.pushAndAssignIDs(E); + extendRegion(E->getLHS()); propagateCounts(getRegion().getCounter(), E->getLHS()); handleFileExit(getEnd(E->getLHS())); @@ -1510,6 +1822,11 @@ extendRegion(E->getRHS()); propagateCounts(getRegionCounter(E), E->getRHS()); + // Process Binary Operator and create MCDC Decision Region if top-level + unsigned NumConds = 0; + if ((NumConds = MCDCBuilder.popAndReturnCondCount(E))) + createDecisionRegion(E, getRegionBitmask(E), NumConds); + // Extract the RHS's Execution Counter. Counter RHSExecCnt = getRegionCounter(E); @@ -1519,13 +1836,28 @@ // Extract the Parent Region Counter. Counter ParentCnt = getRegion().getCounter(); + // Extract the MCDC condition IDs (returns 0 if not needed). + unsigned NextOrID = MCDCBuilder.getNextLOrCondID(); + unsigned NextAndID = MCDCBuilder.getNextLAndCondID(); + unsigned LHSid = MCDCBuilder.getCondID(E->getLHS()); + unsigned RHSid = MCDCBuilder.getCondID(E->getRHS()); + // Create Branch Region around LHS condition. + // MC/DC: For "LHS || RHS" + // - If LHS is TRUE, execution goes to the LHS of the next logical-AND. + // If that does not exist, execution exits (ID == 0). + // - If LHS is FALSE, execution goes to the RHS. createBranchRegion(E->getLHS(), subtractCounters(ParentCnt, RHSExecCnt), - RHSExecCnt); + RHSExecCnt, LHSid, NextAndID, RHSid); // Create Branch Region around RHS condition. + // MC/DC: For "LHS || RHS" + // - If RHS is TRUE, execution goes to LHS of the next logical-AND. + // If that does not exist, execution exits (ID == 0). + // - If RHS is FALSE, execution goes to the LHS of the next logical-OR. + // If that does not exist, execution exits (ID == 0). createBranchRegion(E->getRHS(), subtractCounters(RHSExecCnt, RHSFalseCnt), - RHSFalseCnt); + RHSFalseCnt, RHSid, NextAndID, NextOrID); } void VisitLambdaExpr(const LambdaExpr *LE) { @@ -1556,19 +1888,32 @@ OS << "Gap,"; break; case CounterMappingRegion::BranchRegion: + case CounterMappingRegion::MCDCBranchRegion: OS << "Branch,"; break; + case CounterMappingRegion::MCDCDecisionRegion: + OS << "Decision,"; + break; } OS << "File " << R.FileID << ", " << R.LineStart << ":" << R.ColumnStart << " -> " << R.LineEnd << ":" << R.ColumnEnd << " = "; - Ctx.dump(R.Count, OS); - if (R.Kind == CounterMappingRegion::BranchRegion) { - OS << ", "; - Ctx.dump(R.FalseCount, OS); + if (R.Kind == CounterMappingRegion::MCDCDecisionRegion) { + OS << "M:" << R.Mask << ", C:" << R.NumConditions; + } else { + Ctx.dump(R.Count, OS); + + if (R.Kind == CounterMappingRegion::BranchRegion || + R.Kind == CounterMappingRegion::MCDCBranchRegion) { + OS << ", "; + Ctx.dump(R.FalseCount, OS); + } } + if (R.Kind == CounterMappingRegion::MCDCBranchRegion) + OS << " [" << R.ID << "," << R.TrueID << "," << R.FalseID << "] "; + if (R.Kind == CounterMappingRegion::ExpansionRegion) OS << " (Expanded file = " << R.ExpandedFileID << ")"; OS << "\n"; @@ -1775,8 +2120,9 @@ void CoverageMappingGen::emitCounterMapping(const Decl *D, llvm::raw_ostream &OS) { - assert(CounterMap); - CounterCoverageMappingBuilder Walker(CVM, *CounterMap, SM, LangOpts); + assert(CounterMap && BitmaskMap); + CounterCoverageMappingBuilder Walker(CVM, *CounterMap, *BitmaskMap, + *CondIDMap, SM, LangOpts); Walker.VisitDecl(D); Walker.write(OS); } Index: clang/lib/Driver/ToolChains/Clang.cpp =================================================================== --- clang/lib/Driver/ToolChains/Clang.cpp +++ clang/lib/Driver/ToolChains/Clang.cpp @@ -907,6 +907,7 @@ if (EmitCovData) CmdArgs.push_back("-fprofile-arcs"); + bool CoverageMapping = false; if (Args.hasFlag(options::OPT_fcoverage_mapping, options::OPT_fno_coverage_mapping, false)) { if (!ProfileGenerateArg) @@ -915,6 +916,17 @@ << "-fprofile-instr-generate"; CmdArgs.push_back("-fcoverage-mapping"); + CoverageMapping = true; + } + + if (Args.hasFlag(options::OPT_fmcdc_coverage, options::OPT_fno_mcdc_coverage, + false)) { + if (!CoverageMapping) + D.Diag(clang::diag::err_drv_argument_only_allowed_with) + << "-fmcdc" + << "-fcoverage-mapping"; + + CmdArgs.push_back("-fmcdc"); } if (Arg *A = Args.getLastArg(options::OPT_ffile_compilation_dir_EQ, Index: clang/lib/Driver/ToolChains/Darwin.cpp =================================================================== --- clang/lib/Driver/ToolChains/Darwin.cpp +++ clang/lib/Driver/ToolChains/Darwin.cpp @@ -1365,7 +1365,7 @@ } } - // Align __llvm_prf_{cnts,data} sections to the maximum expected page + // Align __llvm_prf_{cnts,bitmask,data} sections to the maximum expected page // alignment. This allows profile counters to be mmap()'d to disk. Note that // it's not enough to just page-align __llvm_prf_cnts: the following section // must also be page-aligned so that its data is not clobbered by mmap(). @@ -1375,7 +1375,7 @@ // extra alignment also allows the same binary to be used with/without sync // enabled. if (!ForGCOV) { - for (auto IPSK : {llvm::IPSK_cnts, llvm::IPSK_data}) { + for (auto IPSK : {llvm::IPSK_cnts, llvm::IPSK_bitmask, llvm::IPSK_data}) { addSectalignToPage( Args, CmdArgs, "__DATA", llvm::getInstrProfSectionName(IPSK, llvm::Triple::MachO, Index: clang/test/CoverageMapping/branch-constfolded.cpp =================================================================== --- clang/test/CoverageMapping/branch-constfolded.cpp +++ clang/test/CoverageMapping/branch-constfolded.cpp @@ -1,90 +1,91 @@ // Test that branch regions are not generated for constant-folded conditions. // RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++11 -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only -main-file-name branch-constfolded.cpp %s | FileCheck %s +// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++11 -fmcdc -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only -main-file-name branch-constfolded.cpp %s | FileCheck %s -check-prefix=MCDC // CHECK-LABEL: _Z6fand_0b: -bool fand_0(bool a) { +bool fand_0(bool a) { // MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:20 = M:0, C:2 return false && a; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:15 = 0, 0 } // CHECK: Branch,File 0, [[@LINE-1]]:19 -> [[@LINE-1]]:20 = #2, (#1 - #2) // CHECK-LABEL: _Z6fand_1b: -bool fand_1(bool a) { +bool fand_1(bool a) { // MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:19 = M:0, C:2 return a && true; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:11 = #1, (#0 - #1) } // CHECK: Branch,File 0, [[@LINE-1]]:15 -> [[@LINE-1]]:19 = 0, 0 // CHECK-LABEL: _Z6fand_2bb: -bool fand_2(bool a, bool b) { +bool fand_2(bool a, bool b) {// MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:25 = M:0, C:3 return false && a && b; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:15 = 0, 0 } // CHECK: Branch,File 0, [[@LINE-1]]:19 -> [[@LINE-1]]:20 = #4, (#3 - #4) // CHECK: Branch,File 0, [[@LINE-2]]:24 -> [[@LINE-2]]:25 = #2, (#1 - #2) // CHECK-LABEL: _Z6fand_3bb: -bool fand_3(bool a, bool b) { +bool fand_3(bool a, bool b) {// MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:24 = M:0, C:3 return a && true && b; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:11 = #3, (#0 - #3) } // CHECK: Branch,File 0, [[@LINE-1]]:15 -> [[@LINE-1]]:19 = 0, 0 // CHECK: Branch,File 0, [[@LINE-2]]:23 -> [[@LINE-2]]:24 = #2, (#1 - #2) // CHECK-LABEL: _Z6fand_4bb: -bool fand_4(bool a, bool b) { +bool fand_4(bool a, bool b) {// MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:25 = M:0, C:3 return a && b && false; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:11 = #3, (#0 - #3) } // CHECK: Branch,File 0, [[@LINE-1]]:15 -> [[@LINE-1]]:16 = #4, (#3 - #4) // CHECK: Branch,File 0, [[@LINE-2]]:20 -> [[@LINE-2]]:25 = 0, 0 // CHECK-LABEL: _Z6fand_5b: -bool fand_5(bool a) { +bool fand_5(bool a) { // MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:23 = M:0, C:2 return false && true; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:15 = 0, 0 } // CHECK: Branch,File 0, [[@LINE-1]]:19 -> [[@LINE-1]]:23 = 0, 0 // CHECK-LABEL: _Z6fand_6b: -bool fand_6(bool a) { +bool fand_6(bool a) { // MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:19 = M:0, C:2 return true && a; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:14 = 0, 0 } // CHECK: Branch,File 0, [[@LINE-1]]:18 -> [[@LINE-1]]:19 = #2, (#1 - #2) // CHECK-LABEL: _Z6fand_7b: -bool fand_7(bool a) { +bool fand_7(bool a) { // MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:20 = M:0, C:2 return a && false; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:11 = #1, (#0 - #1) } // CHECK: Branch,File 0, [[@LINE-1]]:15 -> [[@LINE-1]]:20 = 0, 0 // CHECK-LABEL: _Z5for_0b: -bool for_0(bool a) { +bool for_0(bool a) { // MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:19 = M:0, C:2 return true || a; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:14 = 0, 0 } // CHECK: Branch,File 0, [[@LINE-1]]:18 -> [[@LINE-1]]:19 = (#1 - #2), #2 // CHECK-LABEL: _Z5for_1b: -bool for_1(bool a) { +bool for_1(bool a) { // MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:20 = M:0, C:2 return a || false; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:11 = (#0 - #1), #1 } // CHECK: Branch,File 0, [[@LINE-1]]:15 -> [[@LINE-1]]:20 = 0, 0 // CHECK-LABEL: _Z5for_2bb: -bool for_2(bool a, bool b) { +bool for_2(bool a, bool b) {// MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:24 = M:0, C:3 return true || a || b; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:14 = 0, 0 } // CHECK: Branch,File 0, [[@LINE-1]]:18 -> [[@LINE-1]]:19 = (#3 - #4), #4 // CHECK: Branch,File 0, [[@LINE-2]]:23 -> [[@LINE-2]]:24 = (#1 - #2), #2 // CHECK-LABEL: _Z5for_3bb: -bool for_3(bool a, bool b) { +bool for_3(bool a, bool b) {// MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:25 = M:0, C:3 return a || false || b; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:11 = (#0 - #3), #3 } // CHECK: Branch,File 0, [[@LINE-1]]:15 -> [[@LINE-1]]:20 = 0, 0 // CHECK: Branch,File 0, [[@LINE-2]]:24 -> [[@LINE-2]]:25 = (#1 - #2), #2 // CHECK-LABEL: _Z5for_4bb: -bool for_4(bool a, bool b) { +bool for_4(bool a, bool b) {// MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:24 = M:0, C:3 return a || b || true; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:11 = (#0 - #3), #3 } // CHECK: Branch,File 0, [[@LINE-1]]:15 -> [[@LINE-1]]:16 = (#3 - #4), #4 // CHECK: Branch,File 0, [[@LINE-2]]:20 -> [[@LINE-2]]:24 = 0, 0 // CHECK-LABEL: _Z5for_5b: -bool for_5(bool a) { +bool for_5(bool a) { // MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:23 = M:0, C:2 return true || false; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:14 = 0, 0 } // CHECK: Branch,File 0, [[@LINE-1]]:18 -> [[@LINE-1]]:23 = 0, 0 // CHECK-LABEL: _Z5for_6b: -bool for_6(bool a) { +bool for_6(bool a) { // MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:20 = M:0, C:2 return false || a; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:15 = 0, 0 } // CHECK: Branch,File 0, [[@LINE-1]]:19 -> [[@LINE-1]]:20 = (#1 - #2), #2 // CHECK-LABEL: _Z5for_7b: -bool for_7(bool a) { +bool for_7(bool a) { // MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1]]:19 = M:0, C:2 return a || true; // CHECK: Branch,File 0, [[@LINE]]:10 -> [[@LINE]]:11 = (#0 - #1), #1 } // CHECK: Branch,File 0, [[@LINE-1]]:15 -> [[@LINE-1]]:19 = 0, 0 Index: clang/test/CoverageMapping/branch-mincounters.cpp =================================================================== --- clang/test/CoverageMapping/branch-mincounters.cpp +++ clang/test/CoverageMapping/branch-mincounters.cpp @@ -2,6 +2,7 @@ // logical operators on branch conditions for branch coverage. // RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++11 -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only -main-file-name branch-logical-mixed.cpp %s | FileCheck %s +// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++11 -fmcdc -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only -main-file-name branch-logical-mixed.cpp %s | FileCheck %s // CHECK-LABEL: _Z5func1ii: Index: clang/test/CoverageMapping/branch-templates.cpp =================================================================== --- clang/test/CoverageMapping/branch-templates.cpp +++ clang/test/CoverageMapping/branch-templates.cpp @@ -2,6 +2,7 @@ // instantiations. // RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++11 -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only -main-file-name branch-templates.cpp %s | FileCheck %s +// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++11 -fmcdc -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only -main-file-name branch-templates.cpp %s | FileCheck %s template void unused(T x) { Index: clang/test/CoverageMapping/if.cpp =================================================================== --- clang/test/CoverageMapping/if.cpp +++ clang/test/CoverageMapping/if.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -mllvm -emptyline-comment-coverage=false -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only -std=c++2b -triple %itanium_abi_triple -main-file-name if.cpp %s | FileCheck %s +// RUN: %clang_cc1 -mllvm -emptyline-comment-coverage=false -fmcdc -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only -std=c++2b -triple %itanium_abi_triple -main-file-name if.cpp %s | FileCheck %s int nop() { return 0; } Index: clang/test/CoverageMapping/logical.cpp =================================================================== --- clang/test/CoverageMapping/logical.cpp +++ clang/test/CoverageMapping/logical.cpp @@ -1,22 +1,24 @@ // RUN: %clang_cc1 -mllvm -emptyline-comment-coverage=false -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only -main-file-name logical.cpp %s | FileCheck %s +// RUN: %clang_cc1 -mllvm -emptyline-comment-coverage=false -fmcdc -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only -main-file-name logical.cpp %s | FileCheck %s -check-prefix=MCDC -int main() { // CHECK: File 0, [[@LINE]]:12 -> [[@LINE+22]]:2 = #0 +int main() { // CHECK: File 0, [[@LINE]]:12 -> [[@LINE+23]]:2 = #0 bool bt = true; - bool bf = false; + bool bf = false; // MCDC: Decision,File 0, [[@LINE+1]]:12 -> [[@LINE+1]]:20 = M:0, C:2 bool a = bt && bf; // CHECK-NEXT: File 0, [[@LINE]]:12 -> [[@LINE]]:14 = #0 // CHECK-NEXT: Branch,File 0, [[@LINE-1]]:12 -> [[@LINE-1]]:14 = #1, (#0 - #1) // CHECK-NEXT: File 0, [[@LINE-2]]:18 -> [[@LINE-2]]:20 = #1 // CHECK-NEXT: Branch,File 0, [[@LINE-3]]:18 -> [[@LINE-3]]:20 = #2, (#1 - #2) - + // MCDC: Decision,File 0, [[@LINE+1]]:7 -> [[@LINE+2]]:9 = M:1, C:2 a = bt && // CHECK-NEXT: File 0, [[@LINE]]:7 -> [[@LINE]]:9 = #0 bf; // CHECK-NEXT: Branch,File 0, [[@LINE-1]]:7 -> [[@LINE-1]]:9 = #3, (#0 - #3) // CHECK-NEXT: File 0, [[@LINE-1]]:7 -> [[@LINE-1]]:9 = #3 // CHECK-NEXT: Branch,File 0, [[@LINE-2]]:7 -> [[@LINE-2]]:9 = #4, (#3 - #4) + // MCDC: Decision,File 0, [[@LINE+1]]:7 -> [[@LINE+1]]:15 = M:2, C:2 a = bf || bt; // CHECK-NEXT: File 0, [[@LINE]]:7 -> [[@LINE]]:9 = #0 // CHECK-NEXT: Branch,File 0, [[@LINE-1]]:7 -> [[@LINE-1]]:9 = (#0 - #5), #5 // CHECK-NEXT: File 0, [[@LINE-2]]:13 -> [[@LINE-2]]:15 = #5 // CHECK-NEXT: Branch,File 0, [[@LINE-3]]:13 -> [[@LINE-3]]:15 = (#5 - #6), #6 - + // MCDC: Decision,File 0, [[@LINE+1]]:7 -> [[@LINE+2]]:9 = M:3, C:2 a = bf || // CHECK-NEXT: File 0, [[@LINE]]:7 -> [[@LINE]]:9 = #0 bt; // CHECK-NEXT: Branch,File 0, [[@LINE-1]]:7 -> [[@LINE-1]]:9 = (#0 - #7), #7 // CHECK-NEXT: File 0, [[@LINE-1]]:7 -> [[@LINE-1]]:9 = #7 Index: clang/test/CoverageMapping/mcdc-error-conditions.cpp =================================================================== --- /dev/null +++ clang/test/CoverageMapping/mcdc-error-conditions.cpp @@ -0,0 +1,7 @@ +// RUN: not %clang_cc1 -triple %itanium_abi_triple -std=c++11 -fmcdc -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only %s 2>&1| FileCheck %s + +bool func_conditions(bool a, bool b, bool c, bool d, bool e, bool f, bool g) { + return a && b && c && d && e && f && g; +} + +// CHECK: error: MC/DC: Unsupported boolean expression; number of conditions{{.*}} exceeds max Index: clang/test/CoverageMapping/mcdc-error-nests.cpp =================================================================== --- /dev/null +++ clang/test/CoverageMapping/mcdc-error-nests.cpp @@ -0,0 +1,10 @@ +// RUN: not %clang_cc1 -triple %itanium_abi_triple -std=c++11 -fmcdc -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only %s 2>&1| FileCheck %s + +// "Split-nest" -- boolean expressions within boolean expressions. +extern bool bar(bool); +bool func_split_nest(bool a, bool b, bool c, bool d, bool e, bool f, bool g) { + bool res = a && b && c && bar(d && e) && f && g; + return bar(res); +} + +// CHECK: error: MC/DC: Unsupported boolean expression; contains an operation with a nested boolean expression. Index: clang/test/CoverageMapping/mcdc-logical-scalar-ids.cpp =================================================================== --- /dev/null +++ clang/test/CoverageMapping/mcdc-logical-scalar-ids.cpp @@ -0,0 +1,108 @@ +// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++11 -fmcdc -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only %s | FileCheck %s + +extern bool bar (bool, bool, bool, bool, bool); +bool func_scalar_and(bool a, bool b, bool c, bool d, bool e, bool f) { + bool res1 = a && b; + bool res2 = a && b && c; + bool res3 = a && b && c && d; + bool res4 = a && b && c && d && e; + bool res5 = a && b && c && d && e && f; + return bar(res1, res2, res3, res4, res5); +} + +// CHECK: Decision,File 0, 5:17 -> 5:23 = M:0, C:2 +// CHECK: Branch,File 0, 5:17 -> 5:18 = #1, (#0 - #1) [1,2,0] +// CHECK: Branch,File 0, 5:22 -> 5:23 = #2, (#1 - #2) [2,0,0] +// CHECK: Decision,File 0, 6:17 -> 6:28 = M:1, C:3 +// CHECK: Branch,File 0, 6:17 -> 6:18 = #5, (#0 - #5) [1,3,0] +// CHECK: Branch,File 0, 6:22 -> 6:23 = #6, (#5 - #6) [3,2,0] +// CHECK: Branch,File 0, 6:27 -> 6:28 = #4, (#3 - #4) [2,0,0] +// CHECK: Decision,File 0, 7:17 -> 7:33 = M:2, C:4 +// CHECK: Branch,File 0, 7:17 -> 7:18 = #11, (#0 - #11) [1,4,0] +// CHECK: Branch,File 0, 7:22 -> 7:23 = #12, (#11 - #12) [4,3,0] +// CHECK: Branch,File 0, 7:27 -> 7:28 = #10, (#9 - #10) [3,2,0] +// CHECK: Branch,File 0, 7:32 -> 7:33 = #8, (#7 - #8) [2,0,0] +// CHECK: Decision,File 0, 8:17 -> 8:38 = M:4, C:5 +// CHECK: Branch,File 0, 8:17 -> 8:18 = #19, (#0 - #19) [1,5,0] +// CHECK: Branch,File 0, 8:22 -> 8:23 = #20, (#19 - #20) [5,4,0] +// CHECK: Branch,File 0, 8:27 -> 8:28 = #18, (#17 - #18) [4,3,0] +// CHECK: Branch,File 0, 8:32 -> 8:33 = #16, (#15 - #16) [3,2,0] +// CHECK: Branch,File 0, 8:37 -> 8:38 = #14, (#13 - #14) [2,0,0] +// CHECK: Decision,File 0, 9:17 -> 9:43 = M:8, C:6 +// CHECK: Branch,File 0, 9:17 -> 9:18 = #29, (#0 - #29) [1,6,0] +// CHECK: Branch,File 0, 9:22 -> 9:23 = #30, (#29 - #30) [6,5,0] +// CHECK: Branch,File 0, 9:27 -> 9:28 = #28, (#27 - #28) [5,4,0] +// CHECK: Branch,File 0, 9:32 -> 9:33 = #26, (#25 - #26) [4,3,0] +// CHECK: Branch,File 0, 9:37 -> 9:38 = #24, (#23 - #24) [3,2,0] +// CHECK: Branch,File 0, 9:42 -> 9:43 = #22, (#21 - #22) [2,0,0] + +bool func_scalar_or(bool a, bool b, bool c, bool d, bool e, bool f) { + bool res1 = a || b; + bool res2 = a || b || c; + bool res3 = a || b || c || d; + bool res4 = a || b || c || d || e; + bool res5 = a || b || c || d || e || f; + return bar(res1, res2, res3, res4, res5); +} + +// CHECK: Decision,File 0, 40:17 -> 40:23 = M:0, C:2 +// CHECK: Branch,File 0, 40:17 -> 40:18 = (#0 - #1), #1 [1,0,2] +// CHECK: Branch,File 0, 40:22 -> 40:23 = (#1 - #2), #2 [2,0,0] +// CHECK: Decision,File 0, 41:17 -> 41:28 = M:1, C:3 +// CHECK: Branch,File 0, 41:17 -> 41:18 = (#0 - #5), #5 [1,0,3] +// CHECK: Branch,File 0, 41:22 -> 41:23 = (#5 - #6), #6 [3,0,2] +// CHECK: Branch,File 0, 41:27 -> 41:28 = (#3 - #4), #4 [2,0,0] +// CHECK: Decision,File 0, 42:17 -> 42:33 = M:2, C:4 +// CHECK: Branch,File 0, 42:17 -> 42:18 = (#0 - #11), #11 [1,0,4] +// CHECK: Branch,File 0, 42:22 -> 42:23 = (#11 - #12), #12 [4,0,3] +// CHECK: Branch,File 0, 42:27 -> 42:28 = (#9 - #10), #10 [3,0,2] +// CHECK: Branch,File 0, 42:32 -> 42:33 = (#7 - #8), #8 [2,0,0] +// CHECK: Decision,File 0, 43:17 -> 43:38 = M:4, C:5 +// CHECK: Branch,File 0, 43:17 -> 43:18 = (#0 - #19), #19 [1,0,5] +// CHECK: Branch,File 0, 43:22 -> 43:23 = (#19 - #20), #20 [5,0,4] +// CHECK: Branch,File 0, 43:27 -> 43:28 = (#17 - #18), #18 [4,0,3] +// CHECK: Branch,File 0, 43:32 -> 43:33 = (#15 - #16), #16 [3,0,2] +// CHECK: Branch,File 0, 43:37 -> 43:38 = (#13 - #14), #14 [2,0,0] +// CHECK: Decision,File 0, 44:17 -> 44:43 = M:8, C:6 +// CHECK: Branch,File 0, 44:17 -> 44:18 = (#0 - #29), #29 [1,0,6] +// CHECK: Branch,File 0, 44:22 -> 44:23 = (#29 - #30), #30 [6,0,5] +// CHECK: Branch,File 0, 44:27 -> 44:28 = (#27 - #28), #28 [5,0,4] +// CHECK: Branch,File 0, 44:32 -> 44:33 = (#25 - #26), #26 [4,0,3] +// CHECK: Branch,File 0, 44:37 -> 44:38 = (#23 - #24), #24 [3,0,2] +// CHECK: Branch,File 0, 44:42 -> 44:43 = (#21 - #22), #22 [2,0,0] + + +bool func_scalar_mix(bool a, bool b, bool c, bool d, bool e, bool f) { + bool res1 = a || b; + bool res2 = a && (b || c); + bool res3 = (a || b) && (c || d); + bool res4 = a && (b || c) && (d || e); + bool res5 = (a || b) && (c || d) && (e || f); + return bar(res1, res2, res3, res4, res5); +} + +// CHECK: Decision,File 0, 76:17 -> 76:23 = M:0, C:2 +// CHECK: Branch,File 0, 76:17 -> 76:18 = (#0 - #1), #1 [1,0,2] +// CHECK: Branch,File 0, 76:22 -> 76:23 = (#1 - #2), #2 [2,0,0] +// CHECK: Decision,File 0, 77:17 -> 77:30 = M:1, C:3 +// CHECK: Branch,File 0, 77:17 -> 77:18 = #3, (#0 - #3) [1,2,0] +// CHECK: Branch,File 0, 77:23 -> 77:24 = (#3 - #4), #4 [2,0,3] +// CHECK: Branch,File 0, 77:28 -> 77:29 = (#4 - #5), #5 [3,0,0] +// CHECK: Decision,File 0, 78:17 -> 78:37 = M:2, C:4 +// CHECK: Branch,File 0, 78:18 -> 78:19 = (#0 - #7), #7 [1,2,3] +// CHECK: Branch,File 0, 78:23 -> 78:24 = (#7 - #8), #8 [3,2,0] +// CHECK: Branch,File 0, 78:30 -> 78:31 = (#6 - #9), #9 [2,0,4] +// CHECK: Branch,File 0, 78:35 -> 78:36 = (#9 - #10), #10 [4,0,0] +// CHECK: Decision,File 0, 79:17 -> 79:42 = M:4, C:5 +// CHECK: Branch,File 0, 79:17 -> 79:18 = #12, (#0 - #12) [1,3,0] +// CHECK: Branch,File 0, 79:23 -> 79:24 = (#12 - #13), #13 [3,2,4] +// CHECK: Branch,File 0, 79:28 -> 79:29 = (#13 - #14), #14 [4,2,0] +// CHECK: Branch,File 0, 79:35 -> 79:36 = (#11 - #15), #15 [2,0,5] +// CHECK: Branch,File 0, 79:40 -> 79:41 = (#15 - #16), #16 [5,0,0] +// CHECK: Decision,File 0, 80:17 -> 80:49 = M:8, C:6 +// CHECK: Branch,File 0, 80:18 -> 80:19 = (#0 - #19), #19 [1,3,4] +// CHECK: Branch,File 0, 80:23 -> 80:24 = (#19 - #20), #20 [4,3,0] +// CHECK: Branch,File 0, 80:30 -> 80:31 = (#18 - #21), #21 [3,2,5] +// CHECK: Branch,File 0, 80:35 -> 80:36 = (#21 - #22), #22 [5,2,0] +// CHECK: Branch,File 0, 80:42 -> 80:43 = (#17 - #23), #23 [2,0,6] +// CHECK: Branch,File 0, 80:47 -> 80:48 = (#23 - #24), #24 [6,0,0] Index: clang/test/CoverageMapping/mcdc-logical-stmt-ids-all.cpp =================================================================== --- /dev/null +++ clang/test/CoverageMapping/mcdc-logical-stmt-ids-all.cpp @@ -0,0 +1,131 @@ +// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++11 -fmcdc -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only %s | FileCheck %s + +bool func_if_and(bool a, bool b, bool c, bool d, bool e, bool f) { + if (a && b && c && d && e && f) + return true; + return false; +} + +// CHECK: Decision,File 0, 4:7 -> 4:33 = M:0, C:6 +// CHECK: Branch,File 0, 4:7 -> 4:8 = #10, (#0 - #10) [1,6,0] +// CHECK: Branch,File 0, 4:12 -> 4:13 = #11, (#10 - #11) [6,5,0] +// CHECK: Branch,File 0, 4:17 -> 4:18 = #9, (#8 - #9) [5,4,0] +// CHECK: Branch,File 0, 4:22 -> 4:23 = #7, (#6 - #7) [4,3,0] +// CHECK: Branch,File 0, 4:27 -> 4:28 = #5, (#4 - #5) [3,2,0] +// CHECK: Branch,File 0, 4:32 -> 4:33 = #3, (#2 - #3) [2,0,0] + +bool func_if_or(bool a, bool b, bool c, bool d, bool e, bool f) { + if (a || b || c || d || e || f) + return true; + return false; +} + +// CHECK: Decision,File 0, 18:7 -> 18:33 = M:0, C:6 +// CHECK: Branch,File 0, 18:7 -> 18:8 = (#0 - #10), #10 [1,0,6] +// CHECK: Branch,File 0, 18:12 -> 18:13 = (#10 - #11), #11 [6,0,5] +// CHECK: Branch,File 0, 18:17 -> 18:18 = (#8 - #9), #9 [5,0,4] +// CHECK: Branch,File 0, 18:22 -> 18:23 = (#6 - #7), #7 [4,0,3] +// CHECK: Branch,File 0, 18:27 -> 18:28 = (#4 - #5), #5 [3,0,2] +// CHECK: Branch,File 0, 18:32 -> 18:33 = (#2 - #3), #3 [2,0,0] + +bool func_while_and(bool a, bool b, bool c, bool d, bool e, bool f) { + while (a && b && c && d && e && f) { return true; } + return false; +} + +// CHECK: Decision,File 0, 32:10 -> 32:36 = M:0, C:6 +// CHECK: Branch,File 0, 32:10 -> 32:11 = #10, (#0 - #10) [1,6,0] +// CHECK: Branch,File 0, 32:15 -> 32:16 = #11, (#10 - #11) [6,5,0] +// CHECK: Branch,File 0, 32:20 -> 32:21 = #9, (#8 - #9) [5,4,0] +// CHECK: Branch,File 0, 32:25 -> 32:26 = #7, (#6 - #7) [4,3,0] +// CHECK: Branch,File 0, 32:30 -> 32:31 = #5, (#4 - #5) [3,2,0] +// CHECK: Branch,File 0, 32:35 -> 32:36 = #3, (#2 - #3) [2,0,0] + +bool func_while_or(bool a, bool b, bool c, bool d, bool e, bool f) { + while (a || b || c || d || e || f) { return true; } + return false; +} + +// CHECK: Decision,File 0, 45:10 -> 45:36 = M:0, C:6 +// CHECK: Branch,File 0, 45:10 -> 45:11 = (#0 - #10), #10 [1,0,6] +// CHECK: Branch,File 0, 45:15 -> 45:16 = (#10 - #11), #11 [6,0,5] +// CHECK: Branch,File 0, 45:20 -> 45:21 = (#8 - #9), #9 [5,0,4] +// CHECK: Branch,File 0, 45:25 -> 45:26 = (#6 - #7), #7 [4,0,3] +// CHECK: Branch,File 0, 45:30 -> 45:31 = (#4 - #5), #5 [3,0,2] +// CHECK: Branch,File 0, 45:35 -> 45:36 = (#2 - #3), #3 [2,0,0] + +bool func_for_and(bool a, bool b, bool c, bool d, bool e, bool f) { + for (;a && b && c && d && e && f;) { return true; } + return false; +} + +// CHECK: Decision,File 0, 58:9 -> 58:35 = M:0, C:6 +// CHECK: Branch,File 0, 58:9 -> 58:10 = #10, (#0 - #10) [1,6,0] +// CHECK: Branch,File 0, 58:14 -> 58:15 = #11, (#10 - #11) [6,5,0] +// CHECK: Branch,File 0, 58:19 -> 58:20 = #9, (#8 - #9) [5,4,0] +// CHECK: Branch,File 0, 58:24 -> 58:25 = #7, (#6 - #7) [4,3,0] +// CHECK: Branch,File 0, 58:29 -> 58:30 = #5, (#4 - #5) [3,2,0] +// CHECK: Branch,File 0, 58:34 -> 58:35 = #3, (#2 - #3) [2,0,0] + +bool func_for_or(bool a, bool b, bool c, bool d, bool e, bool f) { + for (;a || b || c || d || e || f;) { return true; } + return false; +} + +// CHECK: Decision,File 0, 71:9 -> 71:35 = M:0, C:6 +// CHECK: Branch,File 0, 71:9 -> 71:10 = (#0 - #10), #10 [1,0,6] +// CHECK: Branch,File 0, 71:14 -> 71:15 = (#10 - #11), #11 [6,0,5] +// CHECK: Branch,File 0, 71:19 -> 71:20 = (#8 - #9), #9 [5,0,4] +// CHECK: Branch,File 0, 71:24 -> 71:25 = (#6 - #7), #7 [4,0,3] +// CHECK: Branch,File 0, 71:29 -> 71:30 = (#4 - #5), #5 [3,0,2] +// CHECK: Branch,File 0, 71:34 -> 71:35 = (#2 - #3), #3 [2,0,0] + +bool func_do_and(bool a, bool b, bool c, bool d, bool e, bool f) { + do {} while (a && b && c && d && e && f); + return false; +} + +// CHECK: Decision,File 0, 84:16 -> 84:42 = M:0, C:6 +// CHECK: Branch,File 0, 84:16 -> 84:17 = #10, ((#0 + #1) - #10) [1,6,0] +// CHECK: Branch,File 0, 84:21 -> 84:22 = #11, (#10 - #11) [6,5,0] +// CHECK: Branch,File 0, 84:26 -> 84:27 = #9, (#8 - #9) [5,4,0] +// CHECK: Branch,File 0, 84:31 -> 84:32 = #7, (#6 - #7) [4,3,0] +// CHECK: Branch,File 0, 84:36 -> 84:37 = #5, (#4 - #5) [3,2,0] +// CHECK: Branch,File 0, 84:41 -> 84:42 = #3, (#2 - #3) [2,0,0] + +bool func_do_or(bool a, bool b, bool c, bool d, bool e, bool f) { + do {} while (a || b || c || d || e || f); + return false; +} + +// CHECK: Decision,File 0, 97:16 -> 97:42 = M:0, C:6 +// CHECK: Branch,File 0, 97:16 -> 97:17 = ((#0 + #1) - #10), #10 [1,0,6] +// CHECK: Branch,File 0, 97:21 -> 97:22 = (#10 - #11), #11 [6,0,5] +// CHECK: Branch,File 0, 97:26 -> 97:27 = (#8 - #9), #9 [5,0,4] +// CHECK: Branch,File 0, 97:31 -> 97:32 = (#6 - #7), #7 [4,0,3] +// CHECK: Branch,File 0, 97:36 -> 97:37 = (#4 - #5), #5 [3,0,2] +// CHECK: Branch,File 0, 97:41 -> 97:42 = (#2 - #3), #3 [2,0,0] + +bool func_ternary_and(bool a, bool b, bool c, bool d, bool e, bool f) { + return (a && b && c && d && e && f) ? true : false; +} + +// CHECK: Decision,File 0, 110:11 -> 110:37 = M:0, C:6 +// CHECK: Branch,File 0, 110:11 -> 110:12 = #10, (#0 - #10) [1,6,0] +// CHECK: Branch,File 0, 110:16 -> 110:17 = #11, (#10 - #11) [6,5,0] +// CHECK: Branch,File 0, 110:21 -> 110:22 = #9, (#8 - #9) [5,4,0] +// CHECK: Branch,File 0, 110:26 -> 110:27 = #7, (#6 - #7) [4,3,0] +// CHECK: Branch,File 0, 110:31 -> 110:32 = #5, (#4 - #5) [3,2,0] +// CHECK: Branch,File 0, 110:36 -> 110:37 = #3, (#2 - #3) [2,0,0] + +bool func_ternary_or(bool a, bool b, bool c, bool d, bool e, bool f) { + return (a || b || c || d || e || f) ? true : false; +} + +// CHECK: Decision,File 0, 122:11 -> 122:37 = M:0, C:6 +// CHECK: Branch,File 0, 122:11 -> 122:12 = (#0 - #10), #10 [1,0,6] +// CHECK: Branch,File 0, 122:16 -> 122:17 = (#10 - #11), #11 [6,0,5] +// CHECK: Branch,File 0, 122:21 -> 122:22 = (#8 - #9), #9 [5,0,4] +// CHECK: Branch,File 0, 122:26 -> 122:27 = (#6 - #7), #7 [4,0,3] +// CHECK: Branch,File 0, 122:31 -> 122:32 = (#4 - #5), #5 [3,0,2] +// CHECK: Branch,File 0, 122:36 -> 122:37 = (#2 - #3), #3 [2,0,0] Index: clang/test/CoverageMapping/mcdc-logical-stmt-ids.cpp =================================================================== --- /dev/null +++ clang/test/CoverageMapping/mcdc-logical-stmt-ids.cpp @@ -0,0 +1,110 @@ +// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++11 -fmcdc -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only %s | FileCheck %s + +bool func_if_and(bool a, bool b, bool c, bool d, bool e, bool f) { + if (a && b) + if (a && b && c) + if (a && b && c && d) + if (a && b && c && d && e) + if (a && b && c && d && e && f) + return true; + return false; +} + +// CHECK: Decision,File 0, 4:7 -> 4:13 = M:0, C:2 +// CHECK: Branch,File 0, 4:7 -> 4:8 = #2, (#0 - #2) [1,2,0] +// CHECK: Branch,File 0, 4:12 -> 4:13 = #3, (#2 - #3) [2,0,0] +// CHECK: Decision,File 0, 5:9 -> 5:20 = M:1, C:3 +// CHECK: Branch,File 0, 5:9 -> 5:10 = #7, (#1 - #7) [1,3,0] +// CHECK: Branch,File 0, 5:14 -> 5:15 = #8, (#7 - #8) [3,2,0] +// CHECK: Branch,File 0, 5:19 -> 5:20 = #6, (#5 - #6) [2,0,0] +// CHECK: Decision,File 0, 6:11 -> 6:27 = M:2, C:4 +// CHECK: Branch,File 0, 6:11 -> 6:12 = #14, (#4 - #14) [1,4,0] +// CHECK: Branch,File 0, 6:16 -> 6:17 = #15, (#14 - #15) [4,3,0] +// CHECK: Branch,File 0, 6:21 -> 6:22 = #13, (#12 - #13) [3,2,0] +// CHECK: Branch,File 0, 6:26 -> 6:27 = #11, (#10 - #11) [2,0,0] +// CHECK: Decision,File 0, 7:13 -> 7:34 = M:4, C:5 +// CHECK: Branch,File 0, 7:13 -> 7:14 = #23, (#9 - #23) [1,5,0] +// CHECK: Branch,File 0, 7:18 -> 7:19 = #24, (#23 - #24) [5,4,0] +// CHECK: Branch,File 0, 7:23 -> 7:24 = #22, (#21 - #22) [4,3,0] +// CHECK: Branch,File 0, 7:28 -> 7:29 = #20, (#19 - #20) [3,2,0] +// CHECK: Branch,File 0, 7:33 -> 7:34 = #18, (#17 - #18) [2,0,0] +// CHECK: Decision,File 0, 8:16 -> 8:42 = M:8, C:6 +// CHECK: Branch,File 0, 8:16 -> 8:17 = #34, (#16 - #34) [1,6,0] +// CHECK: Branch,File 0, 8:21 -> 8:22 = #35, (#34 - #35) [6,5,0] +// CHECK: Branch,File 0, 8:26 -> 8:27 = #33, (#32 - #33) [5,4,0] +// CHECK: Branch,File 0, 8:31 -> 8:32 = #31, (#30 - #31) [4,3,0] +// CHECK: Branch,File 0, 8:36 -> 8:37 = #29, (#28 - #29) [3,2,0] +// CHECK: Branch,File 0, 8:41 -> 8:42 = #27, (#26 - #27) [2,0,0] + +bool func_if_or(bool a, bool b, bool c, bool d, bool e, bool f) { + if (a || b) + if (a || b || c) + if (a || b || c || d) + if (a || b || c || d || e) + if (a || b || c || d || e || f) + return true; + return false; +} + +// CHECK: Decision,File 0, 40:7 -> 40:13 = M:0, C:2 +// CHECK: Branch,File 0, 40:7 -> 40:8 = (#0 - #2), #2 [1,0,2] +// CHECK: Branch,File 0, 40:12 -> 40:13 = (#2 - #3), #3 [2,0,0] +// CHECK: Decision,File 0, 41:9 -> 41:20 = M:1, C:3 +// CHECK: Branch,File 0, 41:9 -> 41:10 = (#1 - #7), #7 [1,0,3] +// CHECK: Branch,File 0, 41:14 -> 41:15 = (#7 - #8), #8 [3,0,2] +// CHECK: Branch,File 0, 41:19 -> 41:20 = (#5 - #6), #6 [2,0,0] +// CHECK: Decision,File 0, 42:11 -> 42:27 = M:2, C:4 +// CHECK: Branch,File 0, 42:11 -> 42:12 = (#4 - #14), #14 [1,0,4] +// CHECK: Branch,File 0, 42:16 -> 42:17 = (#14 - #15), #15 [4,0,3] +// CHECK: Branch,File 0, 42:21 -> 42:22 = (#12 - #13), #13 [3,0,2] +// CHECK: Branch,File 0, 42:26 -> 42:27 = (#10 - #11), #11 [2,0,0] +// CHECK: Decision,File 0, 43:13 -> 43:34 = M:4, C:5 +// CHECK: Branch,File 0, 43:13 -> 43:14 = (#9 - #23), #23 [1,0,5] +// CHECK: Branch,File 0, 43:18 -> 43:19 = (#23 - #24), #24 [5,0,4] +// CHECK: Branch,File 0, 43:23 -> 43:24 = (#21 - #22), #22 [4,0,3] +// CHECK: Branch,File 0, 43:28 -> 43:29 = (#19 - #20), #20 [3,0,2] +// CHECK: Branch,File 0, 43:33 -> 43:34 = (#17 - #18), #18 [2,0,0] +// CHECK: Decision,File 0, 44:16 -> 44:42 = M:8, C:6 +// CHECK: Branch,File 0, 44:16 -> 44:17 = (#16 - #34), #34 [1,0,6] +// CHECK: Branch,File 0, 44:21 -> 44:22 = (#34 - #35), #35 [6,0,5] +// CHECK: Branch,File 0, 44:26 -> 44:27 = (#32 - #33), #33 [5,0,4] +// CHECK: Branch,File 0, 44:31 -> 44:32 = (#30 - #31), #31 [4,0,3] +// CHECK: Branch,File 0, 44:36 -> 44:37 = (#28 - #29), #29 [3,0,2] +// CHECK: Branch,File 0, 44:41 -> 44:42 = (#26 - #27), #27 [2,0,0] + +bool func_if_mix(bool a, bool b, bool c, bool d, bool e, bool f) { + if (a || b) + if (a && (b || c)) + if ((a || b) && (c || d)) + if (a && (b || c) && (d || e)) + if ((a || b) && (c || d) && (e || f)) + return true; + return false; +} + +// CHECK: Decision,File 0, 76:7 -> 76:13 = M:0, C:2 +// CHECK: Branch,File 0, 76:7 -> 76:8 = (#0 - #2), #2 [1,0,2] +// CHECK: Branch,File 0, 76:12 -> 76:13 = (#2 - #3), #3 [2,0,0] +// CHECK: Decision,File 0, 77:9 -> 77:22 = M:1, C:3 +// CHECK: Branch,File 0, 77:9 -> 77:10 = #5, (#1 - #5) [1,2,0] +// CHECK: Branch,File 0, 77:15 -> 77:16 = (#5 - #6), #6 [2,0,3] +// CHECK: Branch,File 0, 77:20 -> 77:21 = (#6 - #7), #7 [3,0,0] +// CHECK: Decision,File 0, 78:11 -> 78:31 = M:2, C:4 +// CHECK: Branch,File 0, 78:12 -> 78:13 = (#4 - #10), #10 [1,2,3] +// CHECK: Branch,File 0, 78:17 -> 78:18 = (#10 - #11), #11 [3,2,0] +// CHECK: Branch,File 0, 78:24 -> 78:25 = (#9 - #12), #12 [2,0,4] +// CHECK: Branch,File 0, 78:29 -> 78:30 = (#12 - #13), #13 [4,0,0] +// CHECK: Decision,File 0, 79:13 -> 79:38 = M:4, C:5 +// CHECK: Branch,File 0, 79:13 -> 79:14 = #16, (#8 - #16) [1,3,0] +// CHECK: Branch,File 0, 79:19 -> 79:20 = (#16 - #17), #17 [3,2,4] +// CHECK: Branch,File 0, 79:24 -> 79:25 = (#17 - #18), #18 [4,2,0] +// CHECK: Branch,File 0, 79:31 -> 79:32 = (#15 - #19), #19 [2,0,5] +// CHECK: Branch,File 0, 79:36 -> 79:37 = (#19 - #20), #20 [5,0,0] +// CHECK: Decision,File 0, 80:15 -> 80:47 = M:8, C:6 +// CHECK: Branch,File 0, 80:16 -> 80:17 = (#14 - #24), #24 [1,3,4] +// CHECK: Branch,File 0, 80:21 -> 80:22 = (#24 - #25), #25 [4,3,0] +// CHECK: Branch,File 0, 80:28 -> 80:29 = (#23 - #26), #26 [3,2,5] +// CHECK: Branch,File 0, 80:33 -> 80:34 = (#26 - #27), #27 [5,2,0] +// CHECK: Branch,File 0, 80:40 -> 80:41 = (#22 - #28), #28 [2,0,6] +// CHECK: Branch,File 0, 80:45 -> 80:46 = (#28 - #29), #29 [6,0,0] + Index: clang/test/Driver/darwin-ld.c =================================================================== --- clang/test/Driver/darwin-ld.c +++ clang/test/Driver/darwin-ld.c @@ -336,7 +336,7 @@ // RUN: FileCheck -check-prefix=PROFILE_SECTALIGN %s < %t.log // RUN: %clang -target arm64-apple-ios12 -fprofile-instr-generate -### %t.o 2> %t.log // RUN: FileCheck -check-prefix=PROFILE_SECTALIGN %s < %t.log -// PROFILE_SECTALIGN: "-sectalign" "__DATA" "__llvm_prf_cnts" "0x4000" "-sectalign" "__DATA" "__llvm_prf_data" "0x4000" +// PROFILE_SECTALIGN: "-sectalign" "__DATA" "__llvm_prf_cnts" "0x4000" "-sectalign" "__DATA" "__llvm_prf_bits" "0x4000" "-sectalign" "__DATA" "__llvm_prf_data" "0x4000" // RUN: %clang -target x86_64-apple-darwin12 -fprofile-instr-generate -exported_symbols_list /dev/null -### %t.o 2> %t.log // RUN: FileCheck -check-prefix=PROFILE_EXPORT %s < %t.log Index: clang/test/Profile/c-linkage-available_externally.c =================================================================== --- clang/test/Profile/c-linkage-available_externally.c +++ clang/test/Profile/c-linkage-available_externally.c @@ -1,9 +1,11 @@ // Make sure instrumentation data from available_externally functions doesn't // get thrown out and are emitted with the expected linkage. // RUN: %clang_cc1 -no-opaque-pointers -O2 -triple x86_64-apple-macosx10.9 -main-file-name c-linkage-available_externally.c %s -o - -emit-llvm -fprofile-instrument=clang | FileCheck %s +// RUN: %clang_cc1 -fmcdc -no-opaque-pointers -O2 -triple x86_64-apple-macosx10.9 -main-file-name c-linkage-available_externally.c %s -o - -emit-llvm -fprofile-instrument=clang | FileCheck %s -check-prefix=MCDC // CHECK: @__profc_foo = linkonce_odr hidden global [1 x i64] zeroinitializer, section "__DATA,__llvm_prf_cnts", align 8 -// CHECK: @__profd_foo = linkonce_odr hidden global {{.*}} i64 sub (i64 ptrtoint ([1 x i64]* @__profc_foo to i64), i64 ptrtoint ({ i64, i64, i64, i8*, i8*, i32, [2 x i16] }* @__profd_foo to i64)), {{.*}}, section "__DATA,__llvm_prf_data,regular,live_support", align 8 +// MCDC: @__profbm_foo = linkonce_odr hidden global [0 x i8] zeroinitializer, section "__DATA,__llvm_prf_bits", align 1 +// CHECK: @__profd_foo = linkonce_odr hidden global {{.*}} i64 sub (i64 ptrtoint ([1 x i64]* @__profc_foo to i64), i64 ptrtoint ({ i64, i64, i64, i64, i8*, i8*, i32, [2 x i16], i32 }* @__profd_foo to i64)), {{.*}}, section "__DATA,__llvm_prf_data,regular,live_support", align 8 inline int foo(void) { return 1; } int main(void) { Index: clang/test/Profile/c-mcdc-nested-ternary.c =================================================================== --- /dev/null +++ clang/test/Profile/c-mcdc-nested-ternary.c @@ -0,0 +1,68 @@ +// RUN: %clang_cc1 -triple %itanium_abi_triple %s -o - -emit-llvm -fprofile-instrument=clang -fcoverage-mapping -fmcdc | FileCheck %s -check-prefix=MCDC +// RUN: %clang_cc1 -triple %itanium_abi_triple %s -o - -emit-llvm -fprofile-instrument=clang -fcoverage-mapping | FileCheck %s -check-prefix=NOMCDC + +int test(int b, int c, int d, int e, int f) { + return ((b ? c : d) && e && f); +} + +// NOMCDC-NOT: %mcdc.addr +// NOMCDC-NOT: __profbm_test + +// MCDC BOOKKEEPING. +// MCDC: @__profbm_test = private global [1 x i8] zeroinitializer + +// ALLOCATE MCDC TEMP AND ZERO IT. +// MCDC-LABEL: @test( +// MCDC: %mcdc.addr = alloca i32, align 4 +// MCDC: store i32 0, ptr %mcdc.addr, align 4 + +// TERNARY TRUE SHOULD SHIFT ID = 0 FOR CONDITION 'c'. +// MCDC-LABEL: cond.true: +// MCDC: %[[LAB1:[0-9]+]] = load i32, ptr %c.addr, align 4 +// MCDC-DAG: %[[BOOL:tobool[0-9]*]] = icmp ne i32 %[[LAB1]], 0 +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC-DAG: %[[LAB2:[0-9]+]] = zext i1 %[[BOOL]] to i32 +// MCDC-DAG: %[[LAB3:[0-9]+]] = shl i32 %[[LAB2]], 0 +// MCDC-DAG: %[[LAB4:[0-9]+]] = or i32 %[[TEMP]], %[[LAB3]] +// MCDC-DAG: store i32 %[[LAB4]], ptr %mcdc.addr, align 4 + +// TERNARY FALSE SHOULD SHIFT ID = 0 FOR CONDITION 'd'. +// MCDC-LABEL: cond.false: +// MCDC: %[[LAB1:[0-9]+]] = load i32, ptr %d.addr, align 4 +// MCDC-DAG: %[[BOOL:tobool[0-9]*]] = icmp ne i32 %[[LAB1]], 0 +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC-DAG: %[[LAB2:[0-9]+]] = zext i1 %[[BOOL]] to i32 +// MCDC-DAG: %[[LAB3:[0-9]+]] = shl i32 %[[LAB2]], 0 +// MCDC-DAG: %[[LAB4:[0-9]+]] = or i32 %[[TEMP]], %[[LAB3]] +// MCDC-DAG: store i32 %[[LAB4]], ptr %mcdc.addr, align 4 + +// SHIFT SECOND CONDITION WITH ID = 2. +// MCDC: %[[LAB1:[0-9]+]] = load i32, ptr %e.addr, align 4 +// MCDC-DAG: %[[BOOL:tobool[0-9]*]] = icmp ne i32 %[[LAB1]], 0 +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC-DAG: %[[LAB2:[0-9]+]] = zext i1 %[[BOOL]] to i32 +// MCDC-DAG: %[[LAB3:[0-9]+]] = shl i32 %[[LAB2]], 2 +// MCDC-DAG: %[[LAB4:[0-9]+]] = or i32 %[[TEMP]], %[[LAB3]] +// MCDC-DAG: store i32 %[[LAB4]], ptr %mcdc.addr, align 4 + +// SHIFT THIRD CONDITION WITH ID = 1. +// MCDC: %[[LAB1:[0-9]+]] = load i32, ptr %f.addr, align 4 +// MCDC-DAG: %[[BOOL:tobool[0-9]*]] = icmp ne i32 %[[LAB1]], 0 +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC-DAG: %[[LAB2:[0-9]+]] = zext i1 %[[BOOL]] to i32 +// MCDC-DAG: %[[LAB3:[0-9]+]] = shl i32 %[[LAB2]], 1 +// MCDC-DAG: %[[LAB4:[0-9]+]] = or i32 %[[TEMP]], %[[LAB3]] +// MCDC-DAG: store i32 %[[LAB4]], ptr %mcdc.addr, align 4 + +// UPDATE FINAL BITMASK WITH RESULT. +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC: %[[LAB1:[0-9]+]] = lshr i32 %[[TEMP]], 3 +// MCDC: %[[LAB2:[0-9]+]] = zext i32 %[[LAB1]] to i64 +// MCDC: %[[LAB3:[0-9]+]] = add i64 ptrtoint (ptr @__profbm_test to i64), %[[LAB2]] +// MCDC: %[[LAB4:[0-9]+]] = inttoptr i64 %[[LAB3]] to ptr +// MCDC: %[[LAB5:[0-9]+]] = and i32 %[[TEMP]], 7 +// MCDC: %[[LAB6:[0-9]+]] = trunc i32 %[[LAB5]] to i8 +// MCDC: %[[LAB7:[0-9]+]] = shl i8 1, %[[LAB6]] +// MCDC: %mcdc.bits = load i8, ptr %[[LAB4]], align 1 +// MCDC: %[[LAB8:[0-9]+]] = or i8 %mcdc.bits, %[[LAB7]] +// MCDC: store i8 %[[LAB8]], ptr %[[LAB4]], align 1 Index: clang/test/Profile/c-mcdc.c =================================================================== --- /dev/null +++ clang/test/Profile/c-mcdc.c @@ -0,0 +1,85 @@ +// RUN: %clang_cc1 -triple %itanium_abi_triple %s -o - -emit-llvm -fprofile-instrument=clang -fcoverage-mapping -fmcdc | FileCheck %s -check-prefix=MCDC +// RUN: %clang_cc1 -triple %itanium_abi_triple %s -o - -emit-llvm -fprofile-instrument=clang -fcoverage-mapping | FileCheck %s -check-prefix=NOMCDC + +int test(int a, int b, int c, int d, int e, int f) { + return ((a && b) || ((c && d) || (e && f))); +} + +// NOMCDC-NOT: %mcdc.addr +// NOMCDC-NOT: __profbm_test + +// MCDC BOOKKEEPING. +// MCDC: @__profbm_test = private global [8 x i8] zeroinitializer +// MCDC: @__profc_test = private global [9 x i64] zeroinitializer + +// ALLOCATE MCDC TEMP AND ZERO IT. +// MCDC-LABEL: @test( +// MCDC: %mcdc.addr = alloca i32, align 4 +// MCDC: store i32 0, ptr %mcdc.addr, align 4 + +// SHIFT FIRST CONDITION WITH ID = 0. +// MCDC: %[[LAB1:[0-9]+]] = load i32, ptr %a.addr, align 4 +// MCDC-DAG: %[[BOOL:tobool[0-9]*]] = icmp ne i32 %[[LAB1]], 0 +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC-DAG: %[[LAB2:[0-9]+]] = zext i1 %[[BOOL]] to i32 +// MCDC-DAG: %[[LAB3:[0-9]+]] = shl i32 %[[LAB2]], 0 +// MCDC-DAG: %[[LAB4:[0-9]+]] = or i32 %[[TEMP]], %[[LAB3]] +// MCDC-DAG: store i32 %[[LAB4]], ptr %mcdc.addr, align 4 + +// SHIFT SECOND CONDITION WITH ID = 2. +// MCDC: %[[LAB1:[0-9]+]] = load i32, ptr %b.addr, align 4 +// MCDC-DAG: %[[BOOL:tobool[0-9]*]] = icmp ne i32 %[[LAB1]], 0 +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC-DAG: %[[LAB2:[0-9]+]] = zext i1 %[[BOOL]] to i32 +// MCDC-DAG: %[[LAB3:[0-9]+]] = shl i32 %[[LAB2]], 2 +// MCDC-DAG: %[[LAB4:[0-9]+]] = or i32 %[[TEMP]], %[[LAB3]] +// MCDC-DAG: store i32 %[[LAB4]], ptr %mcdc.addr, align 4 + +// SHIFT THIRD CONDITION WITH ID = 1. +// MCDC: %[[LAB1:[0-9]+]] = load i32, ptr %c.addr, align 4 +// MCDC-DAG: %[[BOOL:tobool[0-9]*]] = icmp ne i32 %[[LAB1]], 0 +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC-DAG: %[[LAB2:[0-9]+]] = zext i1 %[[BOOL]] to i32 +// MCDC-DAG: %[[LAB3:[0-9]+]] = shl i32 %[[LAB2]], 1 +// MCDC-DAG: %[[LAB4:[0-9]+]] = or i32 %[[TEMP]], %[[LAB3]] +// MCDC-DAG: store i32 %[[LAB4]], ptr %mcdc.addr, align 4 + +// SHIFT FOURTH CONDITION WITH ID = 4. +// MCDC: %[[LAB1:[0-9]+]] = load i32, ptr %d.addr, align 4 +// MCDC-DAG: %[[BOOL:tobool[0-9]*]] = icmp ne i32 %[[LAB1]], 0 +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC-DAG: %[[LAB2:[0-9]+]] = zext i1 %[[BOOL]] to i32 +// MCDC-DAG: %[[LAB3:[0-9]+]] = shl i32 %[[LAB2]], 4 +// MCDC-DAG: %[[LAB4:[0-9]+]] = or i32 %[[TEMP]], %[[LAB3]] +// MCDC-DAG: store i32 %[[LAB4]], ptr %mcdc.addr, align 4 + +// SHIFT FIFTH CONDITION WITH ID = 3. +// MCDC: %[[LAB1:[0-9]+]] = load i32, ptr %e.addr, align 4 +// MCDC-DAG: %[[BOOL:tobool[0-9]*]] = icmp ne i32 %[[LAB1]], 0 +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC-DAG: %[[LAB2:[0-9]+]] = zext i1 %[[BOOL]] to i32 +// MCDC-DAG: %[[LAB3:[0-9]+]] = shl i32 %[[LAB2]], 3 +// MCDC-DAG: %[[LAB4:[0-9]+]] = or i32 %[[TEMP]], %[[LAB3]] +// MCDC-DAG: store i32 %[[LAB4]], ptr %mcdc.addr, align 4 + +// SHIFT SIXTH CONDITION WITH ID = 5. +// MCDC: %[[LAB1:[0-9]+]] = load i32, ptr %f.addr, align 4 +// MCDC-DAG: %[[BOOL:tobool[0-9]*]] = icmp ne i32 %[[LAB1]], 0 +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC-DAG: %[[LAB2:[0-9]+]] = zext i1 %[[BOOL]] to i32 +// MCDC-DAG: %[[LAB3:[0-9]+]] = shl i32 %[[LAB2]], 5 +// MCDC-DAG: %[[LAB4:[0-9]+]] = or i32 %[[TEMP]], %[[LAB3]] +// MCDC-DAG: store i32 %[[LAB4]], ptr %mcdc.addr, align 4 + +// UPDATE FINAL BITMASK WITH RESULT. +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC: %[[LAB1:[0-9]+]] = lshr i32 %[[TEMP]], 3 +// MCDC: %[[LAB2:[0-9]+]] = zext i32 %[[LAB1]] to i64 +// MCDC: %[[LAB3:[0-9]+]] = add i64 ptrtoint (ptr @__profbm_test to i64), %[[LAB2]] +// MCDC: %[[LAB4:[0-9]+]] = inttoptr i64 %[[LAB3]] to ptr +// MCDC: %[[LAB5:[0-9]+]] = and i32 %[[TEMP]], 7 +// MCDC: %[[LAB6:[0-9]+]] = trunc i32 %[[LAB5]] to i8 +// MCDC: %[[LAB7:[0-9]+]] = shl i8 1, %[[LAB6]] +// MCDC: %mcdc.bits = load i8, ptr %[[LAB4]], align 1 +// MCDC: %[[LAB8:[0-9]+]] = or i8 %mcdc.bits, %[[LAB7]] +// MCDC: store i8 %[[LAB8]], ptr %[[LAB4]], align 1 Index: compiler-rt/include/profile/InstrProfData.inc =================================================================== --- compiler-rt/include/profile/InstrProfData.inc +++ compiler-rt/include/profile/InstrProfData.inc @@ -76,6 +76,7 @@ ConstantInt::get(llvm::Type::getInt64Ty(Ctx), \ Inc->getHash()->getZExtValue())) INSTR_PROF_DATA(const IntPtrT, IntPtrTy, CounterPtr, RelativeCounterPtr) +INSTR_PROF_DATA(const IntPtrT, IntPtrTy, BitmaskPtr, RelativeBitmaskPtr) /* This is used to map function pointers for the indirect call targets to * function name hashes during the conversion from raw to merged profile * data. @@ -87,7 +88,9 @@ INSTR_PROF_DATA(const uint32_t, llvm::Type::getInt32Ty(Ctx), NumCounters, \ ConstantInt::get(llvm::Type::getInt32Ty(Ctx), NumCounters)) INSTR_PROF_DATA(const uint16_t, Int16ArrayTy, NumValueSites[IPVK_Last+1], \ - ConstantArray::get(Int16ArrayTy, Int16ArrayVals)) + ConstantArray::get(Int16ArrayTy, Int16ArrayVals)) \ +INSTR_PROF_DATA(const uint32_t, llvm::Type::getInt32Ty(Ctx), NumBitmaskBytes, \ + ConstantInt::get(llvm::Type::getInt32Ty(Ctx), NumBitmaskBytes)) #undef INSTR_PROF_DATA /* INSTR_PROF_DATA end. */ @@ -134,9 +137,13 @@ /* FIXME: A more accurate name is NumCounters */ INSTR_PROF_RAW_HEADER(uint64_t, CountersSize, CountersSize) INSTR_PROF_RAW_HEADER(uint64_t, PaddingBytesAfterCounters, PaddingBytesAfterCounters) +INSTR_PROF_RAW_HEADER(uint64_t, NumBitmaskBytes, NumBitmaskBytes) +INSTR_PROF_RAW_HEADER(uint64_t, PaddingBytesAfterBitmaskBytes, PaddingBytesAfterBitmaskBytes) INSTR_PROF_RAW_HEADER(uint64_t, NamesSize, NamesSize) INSTR_PROF_RAW_HEADER(uint64_t, CountersDelta, (uintptr_t)CountersBegin - (uintptr_t)DataBegin) +INSTR_PROF_RAW_HEADER(uint64_t, BitmaskDelta, + (uintptr_t)BitmaskBegin - (uintptr_t)DataBegin) INSTR_PROF_RAW_HEADER(uint64_t, NamesDelta, (uintptr_t)NamesBegin) INSTR_PROF_RAW_HEADER(uint64_t, ValueKindLast, IPVK_Last) #undef INSTR_PROF_RAW_HEADER @@ -269,6 +276,9 @@ INSTR_PROF_SECT_ENTRY(IPSK_cnts, \ INSTR_PROF_QUOTE(INSTR_PROF_CNTS_COMMON), \ INSTR_PROF_CNTS_COFF, "__DATA,") +INSTR_PROF_SECT_ENTRY(IPSK_bitmask, \ + INSTR_PROF_QUOTE(INSTR_PROF_BITS_COMMON), \ + INSTR_PROF_BITS_COFF, "__DATA,") INSTR_PROF_SECT_ENTRY(IPSK_name, \ INSTR_PROF_QUOTE(INSTR_PROF_NAME_COMMON), \ INSTR_PROF_NAME_COFF, "__DATA,") @@ -650,9 +660,9 @@ /* Raw profile format version (start from 1). */ #define INSTR_PROF_RAW_VERSION 8 /* Indexed profile format version (start from 1). */ -#define INSTR_PROF_INDEX_VERSION 8 +#define INSTR_PROF_INDEX_VERSION 9 /* Coverage mapping format version (start from 0). */ -#define INSTR_PROF_COVMAP_VERSION 5 +#define INSTR_PROF_COVMAP_VERSION 6 /* Profile version is always of type uint64_t. Reserve the upper 8 bits in the * version for other variants of profile. We set the lowest bit of the upper 8 @@ -686,6 +696,7 @@ #define INSTR_PROF_DATA_COMMON __llvm_prf_data #define INSTR_PROF_NAME_COMMON __llvm_prf_names #define INSTR_PROF_CNTS_COMMON __llvm_prf_cnts +#define INSTR_PROF_BITS_COMMON __llvm_prf_bits #define INSTR_PROF_VALS_COMMON __llvm_prf_vals #define INSTR_PROF_VNODES_COMMON __llvm_prf_vnds #define INSTR_PROF_COVMAP_COMMON __llvm_covmap @@ -697,6 +708,7 @@ #define INSTR_PROF_DATA_COFF ".lprfd$M" #define INSTR_PROF_NAME_COFF ".lprfn$M" #define INSTR_PROF_CNTS_COFF ".lprfc$M" +#define INSTR_PROF_BITS_COFF ".lprfb$M" #define INSTR_PROF_VALS_COFF ".lprfv$M" #define INSTR_PROF_VNODES_COFF ".lprfnd$M" #define INSTR_PROF_COVMAP_COFF ".lcovmap$M" @@ -708,6 +720,7 @@ #define INSTR_PROF_DATA_SECT_NAME INSTR_PROF_DATA_COFF #define INSTR_PROF_NAME_SECT_NAME INSTR_PROF_NAME_COFF #define INSTR_PROF_CNTS_SECT_NAME INSTR_PROF_CNTS_COFF +#define INSTR_PROF_BITS_SECT_NAME INSTR_PROF_BITS_COFF /* Array of pointers. Each pointer points to a list * of value nodes associated with one value site. */ @@ -722,6 +735,7 @@ #define INSTR_PROF_DATA_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_DATA_COMMON) #define INSTR_PROF_NAME_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_NAME_COMMON) #define INSTR_PROF_CNTS_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_CNTS_COMMON) +#define INSTR_PROF_BITS_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_BITS_COMMON) /* Array of pointers. Each pointer points to a list * of value nodes associated with one value site. */ Index: compiler-rt/lib/profile/InstrProfiling.h =================================================================== --- compiler-rt/lib/profile/InstrProfiling.h +++ compiler-rt/lib/profile/InstrProfiling.h @@ -88,6 +88,8 @@ const char *__llvm_profile_end_names(void); char *__llvm_profile_begin_counters(void); char *__llvm_profile_end_counters(void); +char *__llvm_profile_begin_bitmask(void); +char *__llvm_profile_end_bitmask(void); ValueProfNode *__llvm_profile_begin_vnodes(); ValueProfNode *__llvm_profile_end_vnodes(); uint32_t *__llvm_profile_begin_orderfile(); @@ -102,10 +104,10 @@ * \brief Merge profile data from buffer. * * Read profile data form buffer \p Profile and merge with in-process profile - * counters. The client is expected to have checked or already knows the profile - * data in the buffer matches the in-process counter structure before calling - * it. Returns 0 (success) if the profile data is valid. Upon reading - * invalid/corrupted profile data, returns 1 (failure). + * counters and bitmasks. The client is expected to have checked or already + * knows the profile data in the buffer matches the in-process counter + * structure before calling it. Returns 0 (success) if the profile data is + * valid. Upon reading invalid/corrupted profile data, returns 1 (failure). */ int __llvm_profile_merge_from_buffer(const char *Profile, uint64_t Size); @@ -113,8 +115,8 @@ * * Returns 0 (success) if the profile data in buffer \p Profile with size * \p Size was generated by the same binary and therefore matches - * structurally the in-process counters. If the profile data in buffer is - * not compatible, the interface returns 1 (failure). + * structurally the in-process counters and bitmasks. If the profile data in + * buffer is not compatible, the interface returns 1 (failure). */ int __llvm_profile_check_compatibility(const char *Profile, uint64_t Size); @@ -276,6 +278,9 @@ /*! \brief Get the size of the profile counters section in bytes. */ uint64_t __llvm_profile_get_counters_size(const char *Begin, const char *End); +/*! \brief Get the number of bytes in the profile bitmask section. */ +uint64_t __llvm_profile_get_num_bitmask_bytes(const char *Begin, const char *End); + /* ! \brief Given the sizes of the data and counter information, return the * number of padding bytes before and after the counters, and after the names, * in the raw profile. @@ -286,8 +291,9 @@ * needed to achieve that. */ void __llvm_profile_get_padding_sizes_for_counters( - uint64_t DataSize, uint64_t CountersSize, uint64_t NamesSize, - uint64_t *PaddingBytesBeforeCounters, uint64_t *PaddingBytesAfterCounters, + uint64_t DataSize, uint64_t CountersSize, uint64_t NumBitmaskBytes, + uint64_t NamesSize, uint64_t *PaddingBytesBeforeCounters, + uint64_t *PaddingBytesAfterCounters, uint64_t *PaddingBytesAfterBitmask, uint64_t *PaddingBytesAfterNames); /*! Index: compiler-rt/lib/profile/InstrProfiling.c =================================================================== --- compiler-rt/lib/profile/InstrProfiling.c +++ compiler-rt/lib/profile/InstrProfiling.c @@ -49,6 +49,10 @@ (__llvm_profile_get_version() & VARIANT_MASK_BYTE_COVERAGE) ? 0xFF : 0; memset(I, ResetValue, E - I); + I = __llvm_profile_begin_bitmask(); + E = __llvm_profile_end_bitmask(); + memset(I, 0x0, E - I); + const __llvm_profile_data *DataBegin = __llvm_profile_begin_data(); const __llvm_profile_data *DataEnd = __llvm_profile_end_data(); const __llvm_profile_data *DI; Index: compiler-rt/lib/profile/InstrProfilingBuffer.c =================================================================== --- compiler-rt/lib/profile/InstrProfilingBuffer.c +++ compiler-rt/lib/profile/InstrProfilingBuffer.c @@ -43,11 +43,14 @@ const __llvm_profile_data *DataEnd = __llvm_profile_end_data(); const char *CountersBegin = __llvm_profile_begin_counters(); const char *CountersEnd = __llvm_profile_end_counters(); + const char *BitmaskBegin = __llvm_profile_begin_bitmask(); + const char *BitmaskEnd = __llvm_profile_end_bitmask(); const char *NamesBegin = __llvm_profile_begin_names(); const char *NamesEnd = __llvm_profile_end_names(); return __llvm_profile_get_size_for_buffer_internal( - DataBegin, DataEnd, CountersBegin, CountersEnd, NamesBegin, NamesEnd); + DataBegin, DataEnd, CountersBegin, CountersEnd, BitmaskBegin, BitmaskEnd, + NamesBegin, NamesEnd); } COMPILER_RT_VISIBILITY @@ -83,6 +86,11 @@ __llvm_profile_counter_entry_size(); } +COMPILER_RT_VISIBILITY +uint64_t __llvm_profile_get_num_bitmask_bytes(const char *Begin, const char *End) { + return (End - Begin); +} + /// Calculate the number of padding bytes needed to add to \p Offset in order /// for (\p Offset + Padding) to be page-aligned. static uint64_t calculateBytesNeededToPageAlign(uint64_t Offset) { @@ -102,12 +110,14 @@ COMPILER_RT_VISIBILITY void __llvm_profile_get_padding_sizes_for_counters( - uint64_t DataSize, uint64_t CountersSize, uint64_t NamesSize, - uint64_t *PaddingBytesBeforeCounters, uint64_t *PaddingBytesAfterCounters, - uint64_t *PaddingBytesAfterNames) { + uint64_t DataSize, uint64_t CountersSize, uint64_t NumBitmaskBytes, + uint64_t NamesSize, uint64_t *PaddingBytesBeforeCounters, + uint64_t *PaddingBytesAfterCounters, + uint64_t *PaddingBytesAfterBitmaskBytes, uint64_t *PaddingBytesAfterNames) { if (!needsCounterPadding()) { *PaddingBytesBeforeCounters = 0; *PaddingBytesAfterCounters = 0; + *PaddingBytesAfterBitmaskBytes = 0; *PaddingBytesAfterNames = __llvm_profile_get_num_padding_bytes(NamesSize); return; } @@ -117,31 +127,38 @@ *PaddingBytesBeforeCounters = calculateBytesNeededToPageAlign(sizeof(__llvm_profile_header) + DataSize); *PaddingBytesAfterCounters = calculateBytesNeededToPageAlign(CountersSize); + *PaddingBytesAfterBitmaskBytes = + calculateBytesNeededToPageAlign(NumBitmaskBytes); *PaddingBytesAfterNames = calculateBytesNeededToPageAlign(NamesSize); } COMPILER_RT_VISIBILITY uint64_t __llvm_profile_get_size_for_buffer_internal( const __llvm_profile_data *DataBegin, const __llvm_profile_data *DataEnd, - const char *CountersBegin, const char *CountersEnd, const char *NamesBegin, - const char *NamesEnd) { + const char *CountersBegin, const char *CountersEnd, + const char *BitmaskBegin, const char *BitmaskEnd, + const char *NamesBegin, const char *NamesEnd) { /* Match logic in __llvm_profile_write_buffer(). */ const uint64_t NamesSize = (NamesEnd - NamesBegin) * sizeof(char); uint64_t DataSize = __llvm_profile_get_data_size(DataBegin, DataEnd); uint64_t CountersSize = __llvm_profile_get_counters_size(CountersBegin, CountersEnd); + const uint64_t NumBitmaskBytes = + __llvm_profile_get_num_bitmask_bytes(BitmaskBegin, BitmaskEnd); /* Determine how much padding is needed before/after the counters and after * the names. */ uint64_t PaddingBytesBeforeCounters, PaddingBytesAfterCounters, - PaddingBytesAfterNames; + PaddingBytesAfterNames, PaddingBytesAfterBitmaskBytes; __llvm_profile_get_padding_sizes_for_counters( - DataSize, CountersSize, NamesSize, &PaddingBytesBeforeCounters, - &PaddingBytesAfterCounters, &PaddingBytesAfterNames); + DataSize, CountersSize, NumBitmaskBytes, NamesSize, + &PaddingBytesBeforeCounters, &PaddingBytesAfterCounters, + &PaddingBytesAfterBitmaskBytes, &PaddingBytesAfterNames); return sizeof(__llvm_profile_header) + __llvm_write_binary_ids(NULL) + DataSize + PaddingBytesBeforeCounters + CountersSize + - PaddingBytesAfterCounters + NamesSize + PaddingBytesAfterNames; + PaddingBytesAfterCounters + NumBitmaskBytes + + PaddingBytesAfterBitmaskBytes + NamesSize + PaddingBytesAfterNames; } COMPILER_RT_VISIBILITY @@ -159,9 +176,11 @@ COMPILER_RT_VISIBILITY int __llvm_profile_write_buffer_internal( char *Buffer, const __llvm_profile_data *DataBegin, const __llvm_profile_data *DataEnd, const char *CountersBegin, - const char *CountersEnd, const char *NamesBegin, const char *NamesEnd) { + const char *CountersEnd, const char *BitmaskBegin, const char *BitmaskEnd, + const char *NamesBegin, const char *NamesEnd) { ProfDataWriter BufferWriter; initBufferWriter(&BufferWriter, Buffer); return lprofWriteDataImpl(&BufferWriter, DataBegin, DataEnd, CountersBegin, - CountersEnd, 0, NamesBegin, NamesEnd, 0); + CountersEnd, BitmaskBegin, BitmaskEnd, 0, + NamesBegin, NamesEnd, 0); } Index: compiler-rt/lib/profile/InstrProfilingFile.c =================================================================== --- compiler-rt/lib/profile/InstrProfilingFile.c +++ compiler-rt/lib/profile/InstrProfilingFile.c @@ -108,14 +108,18 @@ const __llvm_profile_data *DataEnd = __llvm_profile_end_data(); const char *CountersBegin = __llvm_profile_begin_counters(); const char *CountersEnd = __llvm_profile_end_counters(); + const char *BitmaskBegin = __llvm_profile_begin_bitmask(); + const char *BitmaskEnd = __llvm_profile_end_bitmask(); const char *NamesBegin = __llvm_profile_begin_names(); const char *NamesEnd = __llvm_profile_end_names(); const uint64_t NamesSize = (NamesEnd - NamesBegin) * sizeof(char); uint64_t DataSize = __llvm_profile_get_data_size(DataBegin, DataEnd); uint64_t CountersSize = __llvm_profile_get_counters_size(CountersBegin, CountersEnd); + uint64_t NumBitmaskBytes = + __llvm_profile_get_num_bitmask_bytes(BitmaskBegin, BitmaskEnd); - /* Check that the counter and data sections in this image are + /* Check that the counter, bitmask, and data sections in this image are * page-aligned. */ unsigned PageSize = getpagesize(); if ((intptr_t)CountersBegin % PageSize != 0) { @@ -123,6 +127,11 @@ CountersBegin, PageSize); return 1; } + if ((intptr_t)BitmaskBegin % PageSize != 0) { + PROF_ERR("Bitmask section not page-aligned (start = %p, pagesz = %u).\n", + BitmaskBegin, PageSize); + return 1; + } if ((intptr_t)DataBegin % PageSize != 0) { PROF_ERR("Data section not page-aligned (start = %p, pagesz = %u).\n", DataBegin, PageSize); @@ -132,10 +141,11 @@ /* Determine how much padding is needed before/after the counters and * after the names. */ uint64_t PaddingBytesBeforeCounters, PaddingBytesAfterCounters, - PaddingBytesAfterNames; + PaddingBytesAfterNames, PaddingBytesAfterBitmaskBytes; __llvm_profile_get_padding_sizes_for_counters( - DataSize, CountersSize, NamesSize, &PaddingBytesBeforeCounters, - &PaddingBytesAfterCounters, &PaddingBytesAfterNames); + DataSize, CountersSize, NumBitmaskBytes, NamesSize, + &PaddingBytesBeforeCounters, &PaddingBytesAfterCounters, + &PaddingBytesAfterBitmaskBytes, &PaddingBytesAfterNames); uint64_t PageAlignedCountersLength = CountersSize + PaddingBytesAfterCounters; uint64_t FileOffsetToCounters = CurrentFileOffset + @@ -155,6 +165,32 @@ FileOffsetToCounters); return 1; } + + /* Also mmap MCDC bitmask bytes. If there aren't any bitmask bytes, mmap() + * will fail with EINVAL. */ + if (NumBitmaskBytes == 0) + return 0; + + uint64_t PageAlignedBitmaskLength = NumBitmaskBytes + + PaddingBytesAfterBitmaskBytes; + uint64_t FileOffsetToBitmask = CurrentFileOffset + + sizeof(__llvm_profile_header) + DataSize + + PaddingBytesBeforeCounters + CountersSize + + PaddingBytesAfterCounters; + void *BitmaskMmap = mmap((void *)BitmaskBegin, PageAlignedBitmaskLength, + PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, + Fileno, FileOffsetToBitmask); + if (BitmaskMmap != BitmaskBegin) { + PROF_ERR( + "Continuous counter sync mode is enabled, but mmap() failed (%s).\n" + " - BitmaskBegin: %p\n" + " - PageAlignedBitmaskLength: %" PRIu64 "\n" + " - Fileno: %d\n" + " - FileOffsetToBitmask: %" PRIu64 "\n", + strerror(errno), BitmaskBegin, PageAlignedBitmaskLength, Fileno, + FileOffsetToBitmask); + return 1; + } return 0; } #elif defined(__ELF__) || defined(_WIN32) @@ -197,6 +233,8 @@ const __llvm_profile_data *DataEnd = __llvm_profile_end_data(); const char *CountersBegin = __llvm_profile_begin_counters(); const char *CountersEnd = __llvm_profile_end_counters(); + const char *BitmaskBegin = __llvm_profile_begin_bitmask(); + const char *BitmaskEnd = __llvm_profile_end_bitmask(); uint64_t DataSize = __llvm_profile_get_data_size(DataBegin, DataEnd); /* Get the file size. */ uint64_t FileSize = 0; @@ -218,6 +256,11 @@ /* Return the memory allocated for counters to OS. */ lprofReleaseMemoryPagesToOS((uintptr_t)CountersBegin, (uintptr_t)CountersEnd); + + /* BIAS MODE not supported yet for Bitmask (MCDC). */ + + /* Return the memory allocated for counters to OS. */ + lprofReleaseMemoryPagesToOS((uintptr_t)BitmaskBegin, (uintptr_t)BitmaskEnd); return 0; } #else Index: compiler-rt/lib/profile/InstrProfilingInternal.h =================================================================== --- compiler-rt/lib/profile/InstrProfilingInternal.h +++ compiler-rt/lib/profile/InstrProfilingInternal.h @@ -21,7 +21,8 @@ */ uint64_t __llvm_profile_get_size_for_buffer_internal( const __llvm_profile_data *DataBegin, const __llvm_profile_data *DataEnd, - const char *CountersBegin, const char *CountersEnd, const char *NamesBegin, + const char *CountersBegin, const char *CountersEnd, + const char *BitmaskBegin, const char *BitmaskEnd, const char *NamesBegin, const char *NamesEnd); /*! @@ -36,7 +37,8 @@ int __llvm_profile_write_buffer_internal( char *Buffer, const __llvm_profile_data *DataBegin, const __llvm_profile_data *DataEnd, const char *CountersBegin, - const char *CountersEnd, const char *NamesBegin, const char *NamesEnd); + const char *CountersEnd, const char *BitmaskBegin, const char *BitmaskEnd, + const char *NamesBegin, const char *NamesEnd); /*! * The data structure describing the data to be written by the @@ -153,6 +155,7 @@ const __llvm_profile_data *DataBegin, const __llvm_profile_data *DataEnd, const char *CountersBegin, const char *CountersEnd, + const char *BitmaskBegin, const char *BitmaskEnd, VPDataReaderType *VPDataReader, const char *NamesBegin, const char *NamesEnd, int SkipNameDataWrite); Index: compiler-rt/lib/profile/InstrProfilingMerge.c =================================================================== --- compiler-rt/lib/profile/InstrProfilingMerge.c +++ compiler-rt/lib/profile/InstrProfilingMerge.c @@ -62,6 +62,9 @@ Header->CountersSize != __llvm_profile_get_num_counters(__llvm_profile_begin_counters(), __llvm_profile_end_counters()) || + Header->NumBitmaskBytes != + __llvm_profile_get_num_bitmask_bytes(__llvm_profile_begin_bitmask(), + __llvm_profile_end_bitmask()) || Header->NamesSize != (uint64_t)(__llvm_profile_end_names() - __llvm_profile_begin_names()) || Header->ValueKindLast != IPVK_Last) @@ -70,7 +73,8 @@ if (ProfileSize < sizeof(__llvm_profile_header) + Header->BinaryIdsSize + Header->DataSize * sizeof(__llvm_profile_data) + Header->NamesSize + - Header->CountersSize * __llvm_profile_counter_entry_size()) + Header->CountersSize * __llvm_profile_counter_entry_size() + + Header->NumBitmaskBytes) return 1; for (SrcData = SrcDataStart, @@ -78,7 +82,8 @@ SrcData < SrcDataEnd; ++SrcData, ++DstData) { if (SrcData->NameRef != DstData->NameRef || SrcData->FuncHash != DstData->FuncHash || - SrcData->NumCounters != DstData->NumCounters) + SrcData->NumCounters != DstData->NumCounters || + SrcData->NumBitmaskBytes != DstData->NumBitmaskBytes) return 1; } @@ -108,21 +113,24 @@ __llvm_profile_data *SrcDataStart, *SrcDataEnd, *SrcData, *DstData; __llvm_profile_header *Header = (__llvm_profile_header *)ProfileData; char *SrcCountersStart; + char *SrcBitmaskStart; const char *SrcNameStart; const char *SrcValueProfDataStart, *SrcValueProfData; uintptr_t CountersDelta = Header->CountersDelta; + uintptr_t BitmaskDelta = Header->BitmaskDelta; SrcDataStart = (__llvm_profile_data *)(ProfileData + sizeof(__llvm_profile_header) + Header->BinaryIdsSize); SrcDataEnd = SrcDataStart + Header->DataSize; SrcCountersStart = (char *)SrcDataEnd; - SrcNameStart = SrcCountersStart + + SrcBitmaskStart = SrcCountersStart + Header->CountersSize * __llvm_profile_counter_entry_size(); + SrcNameStart = SrcBitmaskStart + Header->NumBitmaskBytes; SrcValueProfDataStart = SrcNameStart + Header->NamesSize + __llvm_profile_get_num_padding_bytes(Header->NamesSize); - if (SrcNameStart < SrcCountersStart) + if (SrcNameStart < SrcCountersStart || SrcNameStart < SrcBitmaskStart) return 1; for (SrcData = SrcDataStart, @@ -135,6 +143,8 @@ // extend CounterPtr to get the original value. char *DstCounters = (char *)((uintptr_t)DstData + signextIfWin64(DstData->CounterPtr)); + char *DstBitmask = + (char *)((uintptr_t)DstData + signextIfWin64(DstData->BitmaskPtr)); unsigned NVK = 0; // SrcData is a serialized representation of the memory image. We need to @@ -164,6 +174,20 @@ } } + char *SrcBitmask = + SrcBitmaskStart + ((uintptr_t)SrcData->BitmaskPtr - BitmaskDelta); + // BitmaskDelta also needs to be decreased as we advance to the next data + // record. + BitmaskDelta -= sizeof(*SrcData); + unsigned NB = SrcData->NumBitmaskBytes; + // NumBitmaskBytes may legitimately be 0. Just keep going. + if (NB != 0) { + if (SrcBitmask < SrcBitmaskStart || (SrcBitmask + NB) > SrcNameStart) + return 1; + for (unsigned I = 0; I < NB; I++) + DstBitmask[I] |= SrcBitmask[I]; + } + /* Now merge value profile data. */ if (!VPMergeHook) continue; Index: compiler-rt/lib/profile/InstrProfilingPlatformDarwin.c =================================================================== --- compiler-rt/lib/profile/InstrProfilingPlatformDarwin.c +++ compiler-rt/lib/profile/InstrProfilingPlatformDarwin.c @@ -31,6 +31,11 @@ COMPILER_RT_VISIBILITY extern char CountersEnd __asm("section$end$__DATA$" INSTR_PROF_CNTS_SECT_NAME); COMPILER_RT_VISIBILITY +extern char + BitmaskStart __asm("section$start$__DATA$" INSTR_PROF_BITS_SECT_NAME); +COMPILER_RT_VISIBILITY +extern char BitmaskEnd __asm("section$end$__DATA$" INSTR_PROF_BITS_SECT_NAME); +COMPILER_RT_VISIBILITY extern uint32_t OrderFileStart __asm("section$start$__DATA$" INSTR_PROF_ORDERFILE_SECT_NAME); @@ -56,6 +61,10 @@ COMPILER_RT_VISIBILITY char *__llvm_profile_end_counters(void) { return &CountersEnd; } COMPILER_RT_VISIBILITY +char *__llvm_profile_begin_bitmask(void) { return &BitmaskStart; } +COMPILER_RT_VISIBILITY +char *__llvm_profile_end_bitmask(void) { return &BitmaskEnd; } +COMPILER_RT_VISIBILITY uint32_t *__llvm_profile_begin_orderfile(void) { return &OrderFileStart; } COMPILER_RT_VISIBILITY Index: compiler-rt/lib/profile/InstrProfilingPlatformLinux.c =================================================================== --- compiler-rt/lib/profile/InstrProfilingPlatformLinux.c +++ compiler-rt/lib/profile/InstrProfilingPlatformLinux.c @@ -35,6 +35,8 @@ #define PROF_NAME_STOP INSTR_PROF_SECT_STOP(INSTR_PROF_NAME_COMMON) #define PROF_CNTS_START INSTR_PROF_SECT_START(INSTR_PROF_CNTS_COMMON) #define PROF_CNTS_STOP INSTR_PROF_SECT_STOP(INSTR_PROF_CNTS_COMMON) +#define PROF_BITS_START INSTR_PROF_SECT_START(INSTR_PROF_BITS_COMMON) +#define PROF_BITS_STOP INSTR_PROF_SECT_STOP(INSTR_PROF_BITS_COMMON) #define PROF_ORDERFILE_START INSTR_PROF_SECT_START(INSTR_PROF_ORDERFILE_COMMON) #define PROF_VNODES_START INSTR_PROF_SECT_START(INSTR_PROF_VNODES_COMMON) #define PROF_VNODES_STOP INSTR_PROF_SECT_STOP(INSTR_PROF_VNODES_COMMON) @@ -48,6 +50,8 @@ COMPILER_RT_WEAK; extern char PROF_CNTS_START COMPILER_RT_VISIBILITY COMPILER_RT_WEAK; extern char PROF_CNTS_STOP COMPILER_RT_VISIBILITY COMPILER_RT_WEAK; +extern char PROF_BITS_START COMPILER_RT_VISIBILITY COMPILER_RT_WEAK; +extern char PROF_BITS_STOP COMPILER_RT_VISIBILITY COMPILER_RT_WEAK; extern uint32_t PROF_ORDERFILE_START COMPILER_RT_VISIBILITY COMPILER_RT_WEAK; extern char PROF_NAME_START COMPILER_RT_VISIBILITY COMPILER_RT_WEAK; extern char PROF_NAME_STOP COMPILER_RT_VISIBILITY COMPILER_RT_WEAK; @@ -74,6 +78,12 @@ COMPILER_RT_VISIBILITY char *__llvm_profile_end_counters(void) { return &PROF_CNTS_STOP; } +COMPILER_RT_VISIBILITY char *__llvm_profile_begin_bitmask(void) { + return &PROF_BITS_START; +} +COMPILER_RT_VISIBILITY char *__llvm_profile_end_bitmask(void) { + return &PROF_BITS_STOP; +} COMPILER_RT_VISIBILITY uint32_t *__llvm_profile_begin_orderfile(void) { return &PROF_ORDERFILE_START; } @@ -252,6 +262,8 @@ // define these zero length variables in each of the above 4 sections. COMPILER_RT_VISIBILITY int dummy_cnts[0] COMPILER_RT_SECTION( COMPILER_RT_SEG INSTR_PROF_CNTS_SECT_NAME); +COMPILER_RT_VISIBILITY int dummy_bits[0] COMPILER_RT_SECTION( + COMPILER_RT_SEG INSTR_PROF_BITS_SECT_NAME); COMPILER_RT_VISIBILITY int dummy_data[0] COMPILER_RT_SECTION( COMPILER_RT_SEG INSTR_PROF_DATA_SECT_NAME); COMPILER_RT_VISIBILITY const int dummy_name[0] COMPILER_RT_SECTION( Index: compiler-rt/lib/profile/InstrProfilingPlatformOther.c =================================================================== --- compiler-rt/lib/profile/InstrProfilingPlatformOther.c +++ compiler-rt/lib/profile/InstrProfilingPlatformOther.c @@ -88,6 +88,10 @@ char *__llvm_profile_begin_counters(void) { return CountersFirst; } COMPILER_RT_VISIBILITY char *__llvm_profile_end_counters(void) { return CountersLast; } +COMPILER_RT_VISIBILITY +char *__llvm_profile_begin_bitmask(void) { return BitmaskFirst; } +COMPILER_RT_VISIBILITY +char *__llvm_profile_end_bitmask(void) { return BitmaskLast; } /* TODO: correctly set up OrderFileFirst. */ COMPILER_RT_VISIBILITY uint32_t *__llvm_profile_begin_orderfile(void) { return OrderFileFirst; } Index: compiler-rt/lib/profile/InstrProfilingPlatformWindows.c =================================================================== --- compiler-rt/lib/profile/InstrProfilingPlatformWindows.c +++ compiler-rt/lib/profile/InstrProfilingPlatformWindows.c @@ -14,6 +14,7 @@ #if defined(_MSC_VER) /* Merge read-write sections into .data. */ #pragma comment(linker, "/MERGE:.lprfc=.data") +#pragma comment(linker, "/MERGE:.lprfb=.data") #pragma comment(linker, "/MERGE:.lprfd=.data") #pragma comment(linker, "/MERGE:.lprfv=.data") #pragma comment(linker, "/MERGE:.lprfnd=.data") @@ -30,6 +31,8 @@ #pragma section(".lprfd$Z", read, write) #pragma section(".lprfc$A", read, write) #pragma section(".lprfc$Z", read, write) +#pragma section(".lprfb$A", read, write) +#pragma section(".lprfb$Z", read, write) #pragma section(".lorderfile$A", read, write) #pragma section(".lprfnd$A", read, write) #pragma section(".lprfnd$Z", read, write) @@ -43,6 +46,8 @@ char COMPILER_RT_SECTION(".lprfc$A") CountersStart; char COMPILER_RT_SECTION(".lprfc$Z") CountersEnd; +char COMPILER_RT_SECTION(".lprfb$A") BitmaskStart; +char COMPILER_RT_SECTION(".lprfb$Z") BitmaskEnd; uint32_t COMPILER_RT_SECTION(".lorderfile$A") OrderFileStart; ValueProfNode COMPILER_RT_SECTION(".lprfnd$A") VNodesStart; @@ -58,6 +63,8 @@ char *__llvm_profile_begin_counters(void) { return &CountersStart + 1; } char *__llvm_profile_end_counters(void) { return &CountersEnd; } +char *__llvm_profile_begin_bitmask(void) { return &BitmaskStart + 1; } +char *__llvm_profile_end_bitmask(void) { return &BitmaskEnd; } uint32_t *__llvm_profile_begin_orderfile(void) { return &OrderFileStart; } ValueProfNode *__llvm_profile_begin_vnodes(void) { return &VNodesStart + 1; } Index: compiler-rt/lib/profile/InstrProfilingWriter.c =================================================================== --- compiler-rt/lib/profile/InstrProfilingWriter.c +++ compiler-rt/lib/profile/InstrProfilingWriter.c @@ -246,17 +246,20 @@ const __llvm_profile_data *DataEnd = __llvm_profile_end_data(); const char *CountersBegin = __llvm_profile_begin_counters(); const char *CountersEnd = __llvm_profile_end_counters(); + const char *BitmaskBegin = __llvm_profile_begin_bitmask(); + const char *BitmaskEnd = __llvm_profile_end_bitmask(); const char *NamesBegin = __llvm_profile_begin_names(); const char *NamesEnd = __llvm_profile_end_names(); return lprofWriteDataImpl(Writer, DataBegin, DataEnd, CountersBegin, - CountersEnd, VPDataReader, NamesBegin, NamesEnd, - SkipNameDataWrite); + CountersEnd, BitmaskBegin, BitmaskEnd, VPDataReader, + NamesBegin, NamesEnd, SkipNameDataWrite); } COMPILER_RT_VISIBILITY int lprofWriteDataImpl(ProfDataWriter *Writer, const __llvm_profile_data *DataBegin, const __llvm_profile_data *DataEnd, const char *CountersBegin, const char *CountersEnd, + const char *BitmaskBegin, const char *BitmaskEnd, VPDataReaderType *VPDataReader, const char *NamesBegin, const char *NamesEnd, int SkipNameDataWrite) { int DebugInfoCorrelate = @@ -271,6 +274,8 @@ __llvm_profile_get_counters_size(CountersBegin, CountersEnd); const uint64_t NumCounters = __llvm_profile_get_num_counters(CountersBegin, CountersEnd); + const uint64_t NumBitmaskBytes = + __llvm_profile_get_num_bitmask_bytes(BitmaskBegin, BitmaskEnd); const uint64_t NamesSize = DebugInfoCorrelate ? 0 : NamesEnd - NamesBegin; /* Create the header. */ @@ -279,11 +284,11 @@ /* Determine how much padding is needed before/after the counters and after * the names. */ uint64_t PaddingBytesBeforeCounters, PaddingBytesAfterCounters, - PaddingBytesAfterNames; + PaddingBytesAfterNames, PaddingBytesAfterBitmaskBytes; __llvm_profile_get_padding_sizes_for_counters( - DataSectionSize, CountersSectionSize, NamesSize, + DataSectionSize, CountersSectionSize, NumBitmaskBytes, NamesSize, &PaddingBytesBeforeCounters, &PaddingBytesAfterCounters, - &PaddingBytesAfterNames); + &PaddingBytesAfterBitmaskBytes, &PaddingBytesAfterNames); { // TODO: Unfortunately the header's fields are named DataSize and @@ -300,6 +305,7 @@ * CountersDelta to match. */ #ifdef _WIN64 Header.CountersDelta = (uint32_t)Header.CountersDelta; + Header.BitmaskDelta = (uint32_t)Header.BitmaskDelta; #endif /* The data and names sections are omitted in lightweight mode. */ @@ -324,6 +330,8 @@ {NULL, sizeof(uint8_t), PaddingBytesBeforeCounters, 1}, {CountersBegin, sizeof(uint8_t), CountersSectionSize, 0}, {NULL, sizeof(uint8_t), PaddingBytesAfterCounters, 1}, + {BitmaskBegin, sizeof(uint8_t), NumBitmaskBytes, 0}, + {NULL, sizeof(uint8_t), PaddingBytesAfterBitmaskBytes, 1}, {(SkipNameDataWrite || DebugInfoCorrelate) ? NULL : NamesBegin, sizeof(uint8_t), NamesSize, 0}, {NULL, sizeof(uint8_t), PaddingBytesAfterNames, 1}}; Index: compiler-rt/test/profile/ContinuousSyncMode/image-with-mcdc.c =================================================================== --- /dev/null +++ compiler-rt/test/profile/ContinuousSyncMode/image-with-mcdc.c @@ -0,0 +1,26 @@ +// REQUIRES: darwin + +// RUN: %clang_profgen -fcoverage-mapping -fmcdc -O3 -o %t.exe %s +// RUN: env LLVM_PROFILE_FILE="%c%t.profraw" %run %t.exe 3 3 +// RUN: llvm-profdata show --text --all-functions %t.profraw | FileCheck %s + +// CHECK: Num Bitmask Bytes: +// CHECK-NEXT: $1 +// CHECK-NEXT: Bitmask Byte Values: +// CHECK-NEXT: 8 +#include +#include +extern int __llvm_profile_is_continuous_mode_enabled(void); +int main(int argc, char *const argv[]) { + if (!__llvm_profile_is_continuous_mode_enabled()) + return 1; + + if (argc < 3) + return 1; + + if ((atoi(argv[1]) > 2) && (atoi(argv[2]) > 2)) { + printf("Decision Satisfied"); + } + + return 0; +} Index: compiler-rt/test/profile/instrprof-write-buffer-internal.c =================================================================== --- compiler-rt/test/profile/instrprof-write-buffer-internal.c +++ compiler-rt/test/profile/instrprof-write-buffer-internal.c @@ -25,15 +25,20 @@ const char *__llvm_profile_end_names(void); char *__llvm_profile_begin_counters(void); char *__llvm_profile_end_counters(void); +char *__llvm_profile_begin_bitmask(void); +char *__llvm_profile_end_bitmask(void); uint64_t __llvm_profile_get_size_for_buffer_internal( const void *DataBegin, const void *DataEnd, const char *CountersBegin, - const char *CountersEnd, const char *NamesBegin, const char *NamesEnd); + const char *CountersEnd, const char *BitmaskBegin, const char *BitmaskEnd, + const char *NamesBegin, const char *NamesEnd); int __llvm_profile_write_buffer_internal(char *Buffer, const void *DataBegin, const void *DataEnd, const char *CountersBegin, const char *CountersEnd, + const char *BitmaskBegin, + const char *BitmaskEnd, const char *NamesBegin, const char *NamesEnd); @@ -43,12 +48,14 @@ uint64_t bufsize = __llvm_profile_get_size_for_buffer_internal( __llvm_profile_begin_data(), __llvm_profile_end_data(), __llvm_profile_begin_counters(), __llvm_profile_end_counters(), + __llvm_profile_begin_bitmask(), __llvm_profile_end_bitmask(), __llvm_profile_begin_names(), __llvm_profile_end_names()); char *buf = malloc(bufsize); int ret = __llvm_profile_write_buffer_internal(buf, __llvm_profile_begin_data(), __llvm_profile_end_data(), __llvm_profile_begin_counters(), __llvm_profile_end_counters(), + __llvm_profile_begin_bitmask(), __llvm_profile_end_bitmask(), __llvm_profile_begin_names(), __llvm_profile_end_names()); if (ret != 0) { Index: llvm/include/llvm/IR/IntrinsicInst.h =================================================================== --- llvm/include/llvm/IR/IntrinsicInst.h +++ llvm/include/llvm/IR/IntrinsicInst.h @@ -1251,6 +1251,8 @@ ConstantInt *getNumCounters() const; // The index of the counter that this instruction acts on. ConstantInt *getIndex() const; + // The number of bytes used for MCDC bitmasks for the instrumented function. + ConstantInt *getNumBitmaskBytes() const; }; /// This represents the llvm.instrprof.cover intrinsic. @@ -1311,6 +1313,60 @@ } }; +/// This represents the llvm.instrprof.mcdc.parameters intrinsic. +class InstrProfMCDCParameters : public InstrProfInstBase { +public: + static bool classof(const IntrinsicInst *I) { + return I->getIntrinsicID() == Intrinsic::instrprof_mcdc_parameters; + } + static bool classof(const Value *V) { + return isa(V) && classof(cast(V)); + } +}; + +/// This represents the llvm.instrprof.mcdc.bitmask.update intrinsic. +class InstrProfMCDCBitmaskUpdate : public InstrProfInstBase { +public: + static bool classof(const IntrinsicInst *I) { + return I->getIntrinsicID() == Intrinsic::instrprof_mcdc_bitmask_update; + } + static bool classof(const Value *V) { + return isa(V) && classof(cast(V)); + } + + // The index of the bitmask that this instruction acts on. + ConstantInt *getBitmaskIndex() const { + return cast(const_cast(getArgOperand(3))); + } + + Value *getTempAddr() const { + return cast(const_cast(getArgOperand(4))); + } +}; + +/// This represents the llvm.instrprof.mcdc.temp.update intrinsic. +class InstrProfMCDCTempUpdate : public InstrProfInstBase { +public: + static bool classof(const IntrinsicInst *I) { + return I->getIntrinsicID() == Intrinsic::instrprof_mcdc_temp_update; + } + static bool classof(const Value *V) { + return isa(V) && classof(cast(V)); + } + + ConstantInt *getCondID() const { + return cast(const_cast(getArgOperand(2))); + } + + Value *getTempAddr() const { + return cast(const_cast(getArgOperand(3))); + } + + Value *getCondBool() const { + return cast(const_cast(getArgOperand(4))); + } +}; + class PseudoProbeInst : public IntrinsicInst { public: static bool classof(const IntrinsicInst *I) { Index: llvm/include/llvm/IR/Intrinsics.td =================================================================== --- llvm/include/llvm/IR/Intrinsics.td +++ llvm/include/llvm/IR/Intrinsics.td @@ -615,6 +615,21 @@ llvm_i64_ty, llvm_i32_ty, llvm_i32_ty]>; +// A parameter configuration for instrumentation based MCDC profiling. +def int_instrprof_mcdc_parameters : Intrinsic<[], + [llvm_ptr_ty, llvm_i64_ty, + llvm_i32_ty]>; + +// A bitmask update for instrumentation based MCDC profiling. +def int_instrprof_mcdc_bitmask_update : Intrinsic<[], + [llvm_ptr_ty, llvm_i64_ty, + llvm_i32_ty, llvm_i32_ty, llvm_ptr_ty]>; + +// A temp value update for instrumentation based MCDC profiling. +def int_instrprof_mcdc_temp_update : Intrinsic<[], + [llvm_ptr_ty, llvm_i64_ty, + llvm_i32_ty, llvm_ptr_ty, llvm_i1_ty]>; + def int_call_preallocated_setup : DefaultAttrsIntrinsic<[llvm_token_ty], [llvm_i32_ty]>; def int_call_preallocated_arg : DefaultAttrsIntrinsic<[llvm_ptr_ty], [llvm_token_ty, llvm_i32_ty]>; def int_call_preallocated_teardown : DefaultAttrsIntrinsic<[], [llvm_token_ty]>; Index: llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h =================================================================== --- llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h +++ llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h @@ -15,6 +15,7 @@ #define LLVM_PROFILEDATA_COVERAGE_COVERAGEMAPPING_H #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/BitVector.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/Hashing.h" @@ -33,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -226,7 +228,14 @@ /// A BranchRegion represents leaf-level boolean expressions and is /// associated with two counters, each representing the number of times the /// expression evaluates to true or false. - BranchRegion + BranchRegion, + + /// A DecisionRegion represents a top-level boolean expression and is + /// associated with a variable length bitmask counter and condition number. + MCDCDecisionRegion, + + /// A Branch Region can be extended to include IDs to facilitate MC/DC. + MCDCBranchRegion }; /// Primary Counter that is also used for Branch Regions (TrueCount). @@ -235,6 +244,16 @@ /// Secondary Counter used for Branch Regions (FalseCount). Counter FalseCount; + /// Byte Index of Bitmask Coverage Object for a Decision Region (MC/DC only). + unsigned Mask; + + /// Number of Conditions used for a Decision Region (MC/DC only). + unsigned NumConditions; + + /// IDs used to represent a branch region and other branch regions evaluated + /// based on True and False branches (MC/DC only). + unsigned ID, TrueID, FalseID; + unsigned FileID, ExpandedFileID; unsigned LineStart, ColumnStart, LineEnd, ColumnEnd; RegionKind Kind; @@ -246,11 +265,23 @@ LineStart(LineStart), ColumnStart(ColumnStart), LineEnd(LineEnd), ColumnEnd(ColumnEnd), Kind(Kind) {} - CounterMappingRegion(Counter Count, Counter FalseCount, unsigned FileID, + CounterMappingRegion(Counter Count, Counter FalseCount, unsigned Mask, + unsigned NumConditions, unsigned ID, unsigned TrueID, + unsigned FalseID, unsigned FileID, unsigned ExpandedFileID, unsigned LineStart, unsigned ColumnStart, unsigned LineEnd, unsigned ColumnEnd, RegionKind Kind) - : Count(Count), FalseCount(FalseCount), FileID(FileID), + : Count(Count), FalseCount(FalseCount), Mask(Mask), + NumConditions(NumConditions), ID(ID), TrueID(TrueID), FalseID(FalseID), + FileID(FileID), ExpandedFileID(ExpandedFileID), LineStart(LineStart), + ColumnStart(ColumnStart), LineEnd(LineEnd), ColumnEnd(ColumnEnd), + Kind(Kind) {} + + CounterMappingRegion(unsigned Mask, unsigned NumConditions, unsigned FileID, + unsigned ExpandedFileID, unsigned LineStart, + unsigned ColumnStart, unsigned LineEnd, + unsigned ColumnEnd, RegionKind Kind) + : Mask(Mask), NumConditions(NumConditions), FileID(FileID), ExpandedFileID(ExpandedFileID), LineStart(LineStart), ColumnStart(ColumnStart), LineEnd(LineEnd), ColumnEnd(ColumnEnd), Kind(Kind) {} @@ -285,11 +316,23 @@ } static CounterMappingRegion - makeBranchRegion(Counter Count, Counter FalseCount, unsigned FileID, + makeBranchRegion(Counter Count, Counter FalseCount, unsigned ID, + unsigned TrueID, unsigned FalseID, unsigned FileID, unsigned LineStart, unsigned ColumnStart, unsigned LineEnd, unsigned ColumnEnd) { - return CounterMappingRegion(Count, FalseCount, FileID, 0, LineStart, - ColumnStart, LineEnd, ColumnEnd, BranchRegion); + return CounterMappingRegion(Count, FalseCount, 0, 0, ID, TrueID, FalseID, + FileID, 0, LineStart, ColumnStart, LineEnd, + ColumnEnd, + ID == 0 ? BranchRegion : MCDCBranchRegion); + } + + static CounterMappingRegion + makeDecisionRegion(unsigned Mask, unsigned NumConditions, unsigned FileID, + unsigned LineStart, unsigned ColumnStart, unsigned LineEnd, + unsigned ColumnEnd) { + return CounterMappingRegion(Mask, NumConditions, FileID, 0, LineStart, + ColumnStart, LineEnd, ColumnEnd, + MCDCDecisionRegion); } inline LineColPair startLoc() const { @@ -315,11 +358,163 @@ FalseExecutionCount(FalseExecutionCount), Folded(false) {} }; +/// MCDC Record grouping all information together. +struct MCDCRecord { + typedef enum { MCDC_DontCare = -1, MCDC_False = 0, MCDC_True = 1 } CondState; + + using TestVector = std::vector; + using TestVectors = std::vector; + using BoolVector = std::vector; + using TVRowPair = std::pair; + using TVPairMap = llvm::DenseMap; + using CondIDMap = llvm::DenseMap; + using LineColPairMap = llvm::DenseMap; + +private: + CounterMappingRegion Region; + TestVectors TV; + TVPairMap IndepPairs; + BoolVector Folded; + CondIDMap PosToID; + LineColPairMap CondLoc; + +public: + MCDCRecord(CounterMappingRegion Region, TestVectors TV, TVPairMap IndepPairs, + BoolVector Folded, CondIDMap PosToID, LineColPairMap CondLoc) + : Region(Region), TV(TV), IndepPairs(IndepPairs), Folded(Folded), + PosToID(PosToID), CondLoc(CondLoc){}; + + CounterMappingRegion getDecisionRegion() const { return Region; } + unsigned getNumConditions() const { return Region.NumConditions; } + unsigned getNumTestVectors() const { return TV.size(); } + bool isCondFolded(unsigned Condition) const { return Folded[Condition]; } + + CondState getTVCondition(unsigned TestVector, unsigned Condition) { + // Accessing conditions in the TestVectors requires a translation from a + // ordinal position to actual condition ID. This is done via PosToID[]. + return TV[TestVector][PosToID[Condition]]; + } + + CondState getTVResult(unsigned TestVector) { + // The last value for a Test Vector, after its constituent conditions, is + // always the Result. See MCDCRecordProcessor::RecordTestVector(). + return TV[TestVector][getNumConditions()]; + } + + bool isCondIndepPairCovered(unsigned Condition) const { + // Accessing conditions in the TestVector Row Pairs requires a translation + // from a ordinal position to actual condition ID. This is done via + // PosToID[]. + auto it = PosToID.find(Condition); + if (it != PosToID.end()) + return (IndepPairs.find(it->second) != IndepPairs.end()); + llvm_unreachable("Condition ID without an Ordinal mapping"); + } + + TVRowPair getCondIndepPair(unsigned Condition) { + // Accessing conditions in the TestVector Row Pairs requires a translation + // from a ordinal position to actual condition ID. This is done via + // PosToID[]. + assert(isCondIndepPairCovered(Condition)); + return IndepPairs[PosToID[Condition]]; + } + + float getPercentCovered() const { + unsigned folded = 0; + unsigned covered = 0; + for (unsigned c = 0; c < getNumConditions(); c++) { + if (isCondFolded(c)) + folded++; + else if (isCondIndepPairCovered(c)) + covered++; + } + + return ((double)(covered) / (double)(getNumConditions() - folded)) * 100.0; + } + + std::string getConditionHdrStr(unsigned Condition) { + std::ostringstream ss; + ss << "Condition C" << Condition + 1 << " --> ("; + ss << CondLoc[Condition].first << ":" << CondLoc[Condition].second; + ss << ")\n"; + return ss.str(); + } + + std::string getTestVectorHdrStr() { + std::ostringstream ss; + if (getNumTestVectors() == 0) { + ss << "None.\n"; + return ss.str(); + } + for (unsigned i = 0; i < getNumConditions(); i++) { + ss << "C" << i + 1; + if (i != getNumConditions() - 1) + ss << ", "; + } + ss << " Result\n"; + return ss.str(); + } + + std::string getTestVectorStr(unsigned TestVector) { + assert(TestVector < getNumTestVectors()); + std::ostringstream ss; + // Print Individual Conditions + ss << " " << TestVector + 1 << " { "; + for (unsigned Condition = 0; Condition < getNumConditions(); Condition++) { + if (isCondFolded(Condition)) + ss << "C"; + else { + switch (getTVCondition(TestVector, Condition)) { + case MCDCRecord::MCDC_DontCare: + ss << "-"; + break; + case MCDCRecord::MCDC_True: + ss << "T"; + break; + case MCDCRecord::MCDC_False: + ss << "F"; + break; + } + } + if (Condition != getNumConditions() - 1) + ss << ", "; + } + + // Print Result + ss << " = "; + if (getTVResult(TestVector) == MCDC_True) + ss << "T"; + else + ss << "F"; + ss << " }\n"; + + return ss.str(); + } + + std::string getCondCoverageStr(unsigned Condition) { + assert(Condition < getNumConditions()); + std::ostringstream ss; + + ss << " C" << Condition + 1 << "-Pair: "; + if (isCondFolded(Condition)) { + ss << "constant folded\n"; + } else if (isCondIndepPairCovered(Condition)) { + TVRowPair rows = getCondIndepPair(Condition); + ss << "covered: (" << rows.first << ","; + ss << rows.second << ")\n"; + } else + ss << "not covered\n"; + + return ss.str(); + } +}; + /// A Counter mapping context is used to connect the counters, expressions /// and the obtained counter values. class CounterMappingContext { ArrayRef Expressions; ArrayRef CounterValues; + ArrayRef BitmaskBytes; public: CounterMappingContext(ArrayRef Expressions, @@ -327,6 +522,7 @@ : Expressions(Expressions), CounterValues(CounterValues) {} void setCounts(ArrayRef Counts) { CounterValues = Counts; } + void setBitmaskBytes(ArrayRef Bytes) { BitmaskBytes = Bytes; } void dump(const Counter &C, raw_ostream &OS) const; void dump(const Counter &C) const { dump(C, dbgs()); } @@ -335,6 +531,15 @@ /// counter was executed. Expected evaluate(const Counter &C) const; + /// Return the number of times that a region of code associated with this + /// counter was executed. + Expected evaluateBitmask(unsigned Bitmask, unsigned NumCond) const; + + /// Return an MCDC record that indicates executed test vectors and condition + /// pairs. + Expected evaluateMCDCRegion(CounterMappingRegion Region, + BitVector Bitmask, ArrayRef Branches); + unsigned getMaxCounterID(const Counter &C) const; }; @@ -353,6 +558,8 @@ std::vector CountedRegions; /// Branch Regions in the function along with their counts. std::vector CountedBranchRegions; + /// MCDC Records record a DecisionRegion and associated BranchRegions. + std::vector MCDCRecords; /// The number of times this function was executed. uint64_t ExecutionCount = 0; @@ -362,9 +569,12 @@ FunctionRecord(FunctionRecord &&FR) = default; FunctionRecord &operator=(FunctionRecord &&) = default; + void pushMCDCRecord(MCDCRecord Record) { MCDCRecords.push_back(Record); } + void pushRegion(CounterMappingRegion Region, uint64_t Count, uint64_t FalseCount) { - if (Region.Kind == CounterMappingRegion::BranchRegion) { + if (Region.Kind == CounterMappingRegion::BranchRegion || + Region.Kind == CounterMappingRegion::MCDCBranchRegion) { CountedBranchRegions.emplace_back(Region, Count, FalseCount); // If both counters are hard-coded to zero, then this region represents a // constant-folded branch. @@ -535,6 +745,7 @@ std::vector Segments; std::vector Expansions; std::vector BranchRegions; + std::vector MCDCRecords; public: CoverageData() = default; @@ -561,6 +772,9 @@ /// Branches that can be further processed. ArrayRef getBranches() const { return BranchRegions; } + + /// MCDC Records that can be further processed. + ArrayRef getMCDCRecords() const { return MCDCRecords; } }; /// The mapping of profile information to coverage data. @@ -1005,7 +1219,9 @@ // Compilation directory is stored separately and combined with relative // filenames to produce an absolute file path. Version6 = 5, - // The current version is Version6. + // Branch regions extended and Decision Regions added for MC/DC. + Version7 = 6, + // The current version is Version7. CurrentVersion = INSTR_PROF_COVMAP_VERSION }; Index: llvm/include/llvm/ProfileData/InstrProf.h =================================================================== --- llvm/include/llvm/ProfileData/InstrProf.h +++ llvm/include/llvm/ProfileData/InstrProf.h @@ -95,6 +95,9 @@ /// Return the name prefix of profile counter variables. inline StringRef getInstrProfCountersVarPrefix() { return "__profc_"; } +/// Return the name prefix of profile counter variables. +inline StringRef getInstrProfBitmaskVarPrefix() { return "__profbm_"; } + /// Return the name prefix of value profile variables. inline StringRef getInstrProfValuesVarPrefix() { return "__profvp_"; } @@ -323,12 +326,14 @@ invalid_prof, hash_mismatch, count_mismatch, + bitmask_mismatch, counter_overflow, value_site_count_mismatch, compress_failed, uncompress_failed, empty_raw_profile, - zlib_unavailable + zlib_unavailable, + missing_section }; inline std::error_code make_error_code(instrprof_error E) { @@ -729,18 +734,23 @@ /// Profiling information for a single function. struct InstrProfRecord { std::vector Counts; + std::vector BitmaskBytes; InstrProfRecord() = default; InstrProfRecord(std::vector Counts) : Counts(std::move(Counts)) {} + InstrProfRecord(std::vector Counts, + std::vector BitmaskBytes) + : Counts(std::move(Counts)), BitmaskBytes(std::move(BitmaskBytes)) {} InstrProfRecord(InstrProfRecord &&) = default; InstrProfRecord(const InstrProfRecord &RHS) - : Counts(RHS.Counts), + : Counts(RHS.Counts), BitmaskBytes(RHS.BitmaskBytes), ValueData(RHS.ValueData ? std::make_unique(*RHS.ValueData) : nullptr) {} InstrProfRecord &operator=(InstrProfRecord &&) = default; InstrProfRecord &operator=(const InstrProfRecord &RHS) { Counts = RHS.Counts; + BitmaskBytes = RHS.BitmaskBytes; if (!RHS.ValueData) { ValueData = nullptr; return *this; @@ -919,6 +929,11 @@ NamedInstrProfRecord(StringRef Name, uint64_t Hash, std::vector Counts) : InstrProfRecord(std::move(Counts)), Name(Name), Hash(Hash) {} + NamedInstrProfRecord(StringRef Name, uint64_t Hash, + std::vector Counts, + std::vector BitmaskBytes) + : InstrProfRecord(std::move(Counts), std::move(BitmaskBytes)), Name(Name), + Hash(Hash) {} static bool hasCSFlagInHash(uint64_t FuncHash) { return ((FuncHash >> CS_FLAG_IN_FUNC_HASH) & 1); @@ -1050,7 +1065,9 @@ Version7 = 7, // An additional (optional) memory profile type is added. Version8 = 8, - // The current version is 8. + // An additional field is used for bitmask bytes. + Version9 = 9, + // The current version is 9. CurrentVersion = INSTR_PROF_INDEX_VERSION }; const uint64_t Version = ProfVersion::CurrentVersion; Index: llvm/include/llvm/ProfileData/InstrProfCorrelator.h =================================================================== --- llvm/include/llvm/ProfileData/InstrProfCorrelator.h +++ llvm/include/llvm/ProfileData/InstrProfCorrelator.h @@ -137,7 +137,8 @@ Error dumpYaml(raw_ostream &OS) override; void addProbe(StringRef FunctionName, uint64_t CFGHash, IntPtrT CounterOffset, - IntPtrT FunctionPtr, uint32_t NumCounters); + IntPtrT BitmaskOffset, IntPtrT FunctionPtr, + uint32_t NumCounters, uint32_t NumBitmaskBytes); private: InstrProfCorrelatorImpl(InstrProfCorrelatorKind Kind, Index: llvm/include/llvm/ProfileData/InstrProfData.inc =================================================================== --- llvm/include/llvm/ProfileData/InstrProfData.inc +++ llvm/include/llvm/ProfileData/InstrProfData.inc @@ -76,6 +76,7 @@ ConstantInt::get(llvm::Type::getInt64Ty(Ctx), \ Inc->getHash()->getZExtValue())) INSTR_PROF_DATA(const IntPtrT, IntPtrTy, CounterPtr, RelativeCounterPtr) +INSTR_PROF_DATA(const IntPtrT, IntPtrTy, BitmaskPtr, RelativeBitmaskPtr) /* This is used to map function pointers for the indirect call targets to * function name hashes during the conversion from raw to merged profile * data. @@ -87,7 +88,9 @@ INSTR_PROF_DATA(const uint32_t, llvm::Type::getInt32Ty(Ctx), NumCounters, \ ConstantInt::get(llvm::Type::getInt32Ty(Ctx), NumCounters)) INSTR_PROF_DATA(const uint16_t, Int16ArrayTy, NumValueSites[IPVK_Last+1], \ - ConstantArray::get(Int16ArrayTy, Int16ArrayVals)) + ConstantArray::get(Int16ArrayTy, Int16ArrayVals)) \ +INSTR_PROF_DATA(const uint32_t, llvm::Type::getInt32Ty(Ctx), NumBitmaskBytes, \ + ConstantInt::get(llvm::Type::getInt32Ty(Ctx), NumBitmaskBytes)) #undef INSTR_PROF_DATA /* INSTR_PROF_DATA end. */ @@ -134,9 +137,13 @@ /* FIXME: A more accurate name is NumCounters */ INSTR_PROF_RAW_HEADER(uint64_t, CountersSize, CountersSize) INSTR_PROF_RAW_HEADER(uint64_t, PaddingBytesAfterCounters, PaddingBytesAfterCounters) +INSTR_PROF_RAW_HEADER(uint64_t, NumBitmaskBytes, NumBitmaskBytes) +INSTR_PROF_RAW_HEADER(uint64_t, PaddingBytesAfterBitmaskBytes, PaddingBytesAfterBitmaskBytes) INSTR_PROF_RAW_HEADER(uint64_t, NamesSize, NamesSize) INSTR_PROF_RAW_HEADER(uint64_t, CountersDelta, (uintptr_t)CountersBegin - (uintptr_t)DataBegin) +INSTR_PROF_RAW_HEADER(uint64_t, BitmaskDelta, + (uintptr_t)BitmaskBegin - (uintptr_t)DataBegin) INSTR_PROF_RAW_HEADER(uint64_t, NamesDelta, (uintptr_t)NamesBegin) INSTR_PROF_RAW_HEADER(uint64_t, ValueKindLast, IPVK_Last) #undef INSTR_PROF_RAW_HEADER @@ -269,6 +276,9 @@ INSTR_PROF_SECT_ENTRY(IPSK_cnts, \ INSTR_PROF_QUOTE(INSTR_PROF_CNTS_COMMON), \ INSTR_PROF_CNTS_COFF, "__DATA,") +INSTR_PROF_SECT_ENTRY(IPSK_bitmask, \ + INSTR_PROF_QUOTE(INSTR_PROF_BITS_COMMON), \ + INSTR_PROF_BITS_COFF, "__DATA,") INSTR_PROF_SECT_ENTRY(IPSK_name, \ INSTR_PROF_QUOTE(INSTR_PROF_NAME_COMMON), \ INSTR_PROF_NAME_COFF, "__DATA,") @@ -650,9 +660,9 @@ /* Raw profile format version (start from 1). */ #define INSTR_PROF_RAW_VERSION 8 /* Indexed profile format version (start from 1). */ -#define INSTR_PROF_INDEX_VERSION 8 +#define INSTR_PROF_INDEX_VERSION 9 /* Coverage mapping format version (start from 0). */ -#define INSTR_PROF_COVMAP_VERSION 5 +#define INSTR_PROF_COVMAP_VERSION 6 /* Profile version is always of type uint64_t. Reserve the upper 8 bits in the * version for other variants of profile. We set the lowest bit of the upper 8 @@ -686,6 +696,7 @@ #define INSTR_PROF_DATA_COMMON __llvm_prf_data #define INSTR_PROF_NAME_COMMON __llvm_prf_names #define INSTR_PROF_CNTS_COMMON __llvm_prf_cnts +#define INSTR_PROF_BITS_COMMON __llvm_prf_bits #define INSTR_PROF_VALS_COMMON __llvm_prf_vals #define INSTR_PROF_VNODES_COMMON __llvm_prf_vnds #define INSTR_PROF_COVMAP_COMMON __llvm_covmap @@ -697,6 +708,7 @@ #define INSTR_PROF_DATA_COFF ".lprfd$M" #define INSTR_PROF_NAME_COFF ".lprfn$M" #define INSTR_PROF_CNTS_COFF ".lprfc$M" +#define INSTR_PROF_BITS_COFF ".lprfb$M" #define INSTR_PROF_VALS_COFF ".lprfv$M" #define INSTR_PROF_VNODES_COFF ".lprfnd$M" #define INSTR_PROF_COVMAP_COFF ".lcovmap$M" @@ -708,6 +720,7 @@ #define INSTR_PROF_DATA_SECT_NAME INSTR_PROF_DATA_COFF #define INSTR_PROF_NAME_SECT_NAME INSTR_PROF_NAME_COFF #define INSTR_PROF_CNTS_SECT_NAME INSTR_PROF_CNTS_COFF +#define INSTR_PROF_BITS_SECT_NAME INSTR_PROF_BITS_COFF /* Array of pointers. Each pointer points to a list * of value nodes associated with one value site. */ @@ -722,6 +735,7 @@ #define INSTR_PROF_DATA_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_DATA_COMMON) #define INSTR_PROF_NAME_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_NAME_COMMON) #define INSTR_PROF_CNTS_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_CNTS_COMMON) +#define INSTR_PROF_BITS_SECT_NAME INSTR_PROF_QUOTE(INSTR_PROF_BITS_COMMON) /* Array of pointers. Each pointer points to a list * of value nodes associated with one value site. */ Index: llvm/include/llvm/ProfileData/InstrProfReader.h =================================================================== --- llvm/include/llvm/ProfileData/InstrProfReader.h +++ llvm/include/llvm/ProfileData/InstrProfReader.h @@ -282,11 +282,14 @@ // the variant types of the profile. uint64_t Version; uint64_t CountersDelta; + uint64_t BitmaskDelta; uint64_t NamesDelta; const RawInstrProf::ProfileData *Data; const RawInstrProf::ProfileData *DataEnd; const char *CountersStart; const char *CountersEnd; + const char *BitmaskStart; + const char *BitmaskEnd; const char *NamesStart; const char *NamesEnd; // After value profile is all read, this pointer points to @@ -378,6 +381,7 @@ Error readName(NamedInstrProfRecord &Record); Error readFuncHash(NamedInstrProfRecord &Record); Error readRawCounts(InstrProfRecord &Record); + Error readRawBitmaskBytes(InstrProfRecord &Record); Error readValueProfilingData(InstrProfRecord &Record); bool atEnd() const { return Data == DataEnd; } @@ -390,6 +394,7 @@ // As we advance to the next record, we maintain the correct CountersDelta // with respect to the next record. CountersDelta -= sizeof(*Data); + BitmaskDelta -= sizeof(*Data); } Data++; ValueDataStart += CurValueDataSize; @@ -665,6 +670,10 @@ Error getFunctionCounts(StringRef FuncName, uint64_t FuncHash, std::vector &Counts); + /// Fill Bitmask Bytes with the profile data for the given function name. + Error getFunctionBitmaskBytes(StringRef FuncName, uint64_t FuncHash, + std::vector &BitmaskBytes); + /// Return the maximum of all known function counts. /// \c UseCS indicates whether to use the context-sensitive count. uint64_t getMaximumFunctionCount(bool UseCS) { Index: llvm/include/llvm/Transforms/Instrumentation/InstrProfiling.h =================================================================== --- llvm/include/llvm/Transforms/Instrumentation/InstrProfiling.h +++ llvm/include/llvm/Transforms/Instrumentation/InstrProfiling.h @@ -50,6 +50,7 @@ uint32_t NumValueSites[IPVK_Last + 1]; GlobalVariable *RegionCounters = nullptr; GlobalVariable *DataVar = nullptr; + GlobalVariable *RegionBitmasks = nullptr; PerFunctionProfileData() { memset(NumValueSites, 0, sizeof(uint32_t) * (IPVK_Last + 1)); @@ -101,6 +102,14 @@ /// Force emitting of name vars for unused functions. void lowerCoverageData(GlobalVariable *CoverageNamesVar); + /// Replace instrprof.mcdc.bitmask.update with a shift and or instruction + /// using the index represented by the a temp value into a bitmask. + void lowerMCDCBitmaskUpdate(InstrProfMCDCBitmaskUpdate *Ins); + + /// Replace instrprof.mcdc.temp.update with a shift and or instruction using + /// the corresponding condition ID. + void lowerMCDCTempUpdate(InstrProfMCDCTempUpdate *Ins); + /// Compute the address of the counter value that this profiling instruction /// acts on. Value *getCounterAddress(InstrProfInstBase *I); @@ -115,6 +124,30 @@ GlobalVariable *createRegionCounters(InstrProfInstBase *Inc, StringRef Name, GlobalValue::LinkageTypes Linkage); + /// Compute the address of the bitmask value that this profiling instruction + /// acts on. + Value *getBitmaskAddress(InstrProfMCDCBitmaskUpdate *I); + + /// Get the region bitmasks for an increment, creating them if necessary. + /// + /// If the bitmask array doesn't yet exist, the profile data variables + /// referring to them will also be created. + GlobalVariable *getOrCreateRegionBitmasks(InstrProfInstBase *Inc); + + /// Create the region bitmasks. + GlobalVariable *createRegionBitmasks(InstrProfInstBase *Inc, StringRef Name, + GlobalValue::LinkageTypes Linkage); + + /// Set Comdat property of GV, if required. + void maybeSetComdat(GlobalVariable *GV, Function *Fn, StringRef VarName); + + /// Setup the sections into which counters and bitmasks are allocated. + GlobalVariable *setupProfileSection(InstrProfInstBase *Inc, + InstrProfSectKind IPSK); + + /// Create INSTR_PROF_DATA variable for counters and bitmasks. + void createDataVariable(InstrProfInstBase *Inc, InstrProfInstBase *Update); + /// Emit the section with compressed function names. void emitNameData(); Index: llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp =================================================================== --- llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -6985,6 +6985,10 @@ llvm_unreachable("instrprof failed to lower an increment"); case Intrinsic::instrprof_value_profile: llvm_unreachable("instrprof failed to lower a value profiling call"); + case Intrinsic::instrprof_mcdc_bitmask_update: + llvm_unreachable("instrprof failed to lower an mcdc bitmask update"); + case Intrinsic::instrprof_mcdc_temp_update: + llvm_unreachable("instrprof failed to lower an mcdc tempval update"); case Intrinsic::localescape: { MachineFunction &MF = DAG.getMachineFunction(); const TargetInstrInfo *TII = DAG.getSubtarget().getInstrInfo(); Index: llvm/lib/IR/IntrinsicInst.cpp =================================================================== --- llvm/lib/IR/IntrinsicInst.cpp +++ llvm/lib/IR/IntrinsicInst.cpp @@ -230,6 +230,10 @@ return ConstantInt::get(Type::getInt64Ty(Context), 1); } +ConstantInt *InstrProfInstBase::getNumBitmaskBytes() const { + return cast(const_cast(getArgOperand(2))); +} + Optional ConstrainedFPIntrinsic::getRoundingMode() const { unsigned NumOperands = arg_size(); Metadata *MD = nullptr; Index: llvm/lib/ProfileData/Coverage/CoverageMapping.cpp =================================================================== --- llvm/lib/ProfileData/Coverage/CoverageMapping.cpp +++ llvm/lib/ProfileData/Coverage/CoverageMapping.cpp @@ -187,6 +187,196 @@ llvm_unreachable("Unhandled CounterKind"); } +Expected CounterMappingContext::evaluateBitmask(unsigned ID, + unsigned NC) const { + unsigned SizeInBits = llvm::alignTo(1L << NC, CHAR_BIT); + unsigned SizeInBytes = SizeInBits / CHAR_BIT; + + ArrayRef Bytes(&BitmaskBytes[ID], SizeInBytes); + + // Mask each bitmask byte into the BitVector. Go in reverse so that the + // bitvector can just be shifted over by one byte on each iteration. + BitVector Result(SizeInBits, false); + for (auto byte = std::rbegin(Bytes); byte != std::rend(Bytes); ++byte) { + uint32_t data = *byte; + Result <<= CHAR_BIT; + Result.setBitsInMask(&data, 1); + } + return Result; +} + +class MCDCRecordProcessor { + BitVector &Bitmask; + CounterMappingRegion &Region; + ArrayRef Branches; + unsigned NumConditions; + llvm::DenseMap Map; + + MCDCRecord::BoolVector Folded; + MCDCRecord::TVPairMap IndepPairs; + MCDCRecord::TestVectors TestVectors; + MCDCRecord::TestVectors ExecVectors; + +public: + MCDCRecordProcessor(BitVector &Bitmask, CounterMappingRegion &Region, + ArrayRef Branches) + : Bitmask(Bitmask), Region(Region), Branches(Branches), + NumConditions(Region.NumConditions), Folded(NumConditions, false), + IndepPairs(NumConditions), TestVectors(pow(2, NumConditions)) {} + +private: + void recordTestVector(MCDCRecord::TestVector &TV, + MCDCRecord::CondState Result) { + // Calculate an index that is used to identify the test vector in a vector + // of test vectors. This index also corresponds to the index values of an + // MCDC Region's bitmask (see findExecutedTestVectors()). + unsigned idx = 0; + for (auto Cond = std::rbegin(TV); Cond != std::rend(TV); ++Cond) { + idx <<= 1; + idx |= (*Cond == MCDCRecord::MCDC_True) ? 0x1 : 0x0; + } + + // Copy the completed test vector to the vector of testvectors. + TestVectors[idx] = TV; + + // The final value (T,F) is equal to the last non-dontcare state on the + // path (in a short-circuiting system). + TestVectors[idx].push_back(Result); + } + + void buildTestVector(MCDCRecord::TestVector &TV, unsigned ID = 1) { + // Branch regions are hashed based on an ID. + const CounterMappingRegion *Branch = Map[ID]; + + // An ID of '0' indicates the end of a test vector, at which point, the + // test vector can be copied off, and the algorithm can keep going. + + // Follow "True" Path in Test Vector. + TV[ID - 1] = MCDCRecord::MCDC_True; + if (Branch->TrueID > 0) + buildTestVector(TV, Branch->TrueID); + else + recordTestVector(TV, MCDCRecord::MCDC_True); + + // Follow "False" Path in Test Vector. + TV[ID - 1] = MCDCRecord::MCDC_False; + if (Branch->FalseID > 0) + buildTestVector(TV, Branch->FalseID); + else + recordTestVector(TV, MCDCRecord::MCDC_False); + + // Reset back to DontCare + TV[ID - 1] = MCDCRecord::MCDC_DontCare; + } + + void findExecutedTestVectors(BitVector &Bitmask) { + // Walk the bits in the bitmask. A bit set to '1' indicates that the test + // vector at the corresponding index was executed during a test run. + for (unsigned i = 0; i < Bitmask.size(); i++) + if (Bitmask[i] == 1) { + assert(!TestVectors[i].empty() && "Test Vector doesn't exist."); + ExecVectors.push_back(TestVectors[i]); + } + } + + bool matchTestVectors(unsigned Aidx, unsigned Bidx, unsigned C) { + const MCDCRecord::TestVector &A = ExecVectors[Aidx]; + const MCDCRecord::TestVector &B = ExecVectors[Bidx]; + + // If condition values in both A and B aren't opposites, no match. + if (!((A[C] ^ B[C]) == 1)) + return false; + + // If the results of both A and B aren't opposites, no match. + if (!((A[NumConditions] ^ B[NumConditions]) == 1)) + return false; + + // If just one of the other conditions don't match, no match. + for (unsigned i = 0; i < NumConditions; i++) { + if (i == C) + continue; + if (A[i] != MCDCRecord::MCDC_DontCare && + B[i] != MCDCRecord::MCDC_DontCare && A[i] != B[i]) + return false; + } + + // Otherwise, match. + return true; + } + + void findIndependencePairs() { + unsigned NumTVs = ExecVectors.size(); + + // For each condition. + for (unsigned c = 0; c < NumConditions; c++) { + bool pair_found = false; + + // For each executed test vector. + for (unsigned i = 0; !pair_found && i < NumTVs; i++) { + + // Compared to every other executed test vector. + for (unsigned j = 0; !pair_found && j < NumTVs; j++) { + if (i == j) + continue; + + // If a matching pair of vectors is found, record them. + if ((pair_found = matchTestVectors(i, j, c))) + IndepPairs[c] = std::make_pair(i + 1, j + 1); + } + } + } + } + +public: + MCDCRecord processMCDCRecord() { + unsigned i = 0; + MCDCRecord::CondIDMap PosToID; + MCDCRecord::LineColPairMap CondLoc; + + // Walk the Record's BranchRegions (representing Conditions) in order to: + // - Hash the condition based on its corresponding ID. This will be used to + // calculate the test vectors. + // - Keep a map of the condition's ordinal position (1, 2, 3, 4) to its + // actual ID. This will be used to visualize the conditions in the + // correct order. + // - Keep track of the condition source location. This will be used to + // visualize where the condition is. + // - Record whether the condition is constant folded so that we exclude it + // from being measured. + for (const auto &B : Branches) { + Map[B.ID] = &B; + PosToID[i] = B.ID - 1; + CondLoc[i] = B.startLoc(); + Folded[i++] = (B.Count.isZero() && B.FalseCount.isZero()); + } + + // Initialize a base test vector as 'DontCare'. + MCDCRecord::TestVector TV(NumConditions, MCDCRecord::MCDC_DontCare); + + // Use the base test vector to build the list of all possible test vectors. + buildTestVector(TV); + + // Using Profile Bitmask from runtime, mark the executed test vectors. + findExecutedTestVectors(Bitmask); + + // Compare executed test vectors against each other to find an independence + // pairs for each condition. This processing takes the most time. + findIndependencePairs(); + + // Record Test vectors, executed vectors, and independence pairs. + MCDCRecord Res(Region, ExecVectors, IndepPairs, Folded, PosToID, CondLoc); + return Res; + } +}; + +Expected CounterMappingContext::evaluateMCDCRegion( + CounterMappingRegion Region, BitVector Bitmask, + ArrayRef Branches) { + + MCDCRecordProcessor MCDCProcessor(Bitmask, Region, Branches); + return MCDCProcessor.processMCDCRecord(); +} + unsigned CounterMappingContext::getMaxCounterID(const Counter &C) const { switch (C.getKind()) { case Counter::Zero: @@ -229,6 +419,24 @@ return MaxCounterID; } +static unsigned getMaxBitmaskSize(const CounterMappingContext &Ctx, + const CoverageMappingRecord &Record) { + unsigned MaxBitmaskID = 0; + unsigned NumConditions = 0; + // The last DecisionRegion has the highest bitmask byte index used in the + // function, which when combined with its number of conditions, yields the + // full bitmask size. + for (const auto &Region : reverse(Record.MappingRegions)) { + if (Region.Kind == CounterMappingRegion::MCDCDecisionRegion) { + MaxBitmaskID = Region.Mask; + NumConditions = Region.NumConditions; + break; + } + } + unsigned SizeInBits = llvm::alignTo(1L << NumConditions, CHAR_BIT); + return MaxBitmaskID + (SizeInBits / CHAR_BIT); +} + Error CoverageMapping::loadFunctionRecord( const CoverageMappingRecord &Record, IndexedInstrProfReader &ProfileReader) { @@ -257,6 +465,20 @@ } Ctx.setCounts(Counts); + std::vector BitmaskBytes; + if (Error E = ProfileReader.getFunctionBitmaskBytes( + Record.FunctionName, Record.FunctionHash, BitmaskBytes)) { + instrprof_error IPE = InstrProfError::take(std::move(E)); + if (IPE == instrprof_error::hash_mismatch) { + FuncHashMismatches.emplace_back(std::string(Record.FunctionName), + Record.FunctionHash); + return Error::success(); + } else if (IPE != instrprof_error::unknown_function) + return make_error(IPE); + BitmaskBytes.assign(getMaxBitmaskSize(Ctx, Record) + 1, 0); + } + Ctx.setBitmaskBytes(BitmaskBytes); + assert(!Record.MappingRegions.empty() && "Function has no regions"); // This coverage record is a zero region for a function that's unused in @@ -268,8 +490,20 @@ Record.MappingRegions[0].Count.isZero() && Counts[0] > 0) return Error::success(); + unsigned NumConds = 0; + const CounterMappingRegion *MCDCDecision; + std::vector MCDCBranches; + FunctionRecord Function(OrigFuncName, Record.Filenames); for (const auto &Region : Record.MappingRegions) { + // If an MCDC DecisionRegion is seen, track the BranchRegions that follow + // it according to Region.NumConditions. + if (Region.Kind == CounterMappingRegion::MCDCDecisionRegion) { + assert(NumConds == 0); + MCDCDecision = &Region; + NumConds = Region.NumConditions; + continue; + } Expected ExecutionCount = Ctx.evaluate(Region.Count); if (auto E = ExecutionCount.takeError()) { consumeError(std::move(E)); @@ -281,6 +515,40 @@ return Error::success(); } Function.pushRegion(Region, *ExecutionCount, *AltExecutionCount); + + // If a MCDCDecisionRegion was seen as well as the BranchRegions that + // correspond to it stored in a vector, according to the number of + // conditions recorded for the region. + if (NumConds > 0 && Region.Kind == CounterMappingRegion::MCDCBranchRegion) { + MCDCBranches.push_back(Region); + if (--NumConds == 0) { + // Evaluating the test vector bitmask for the decision region entails + // calculating precisely what bits are pertinent to this region alone. + // This is calculated based on the recorded offset into the global + // profile bitmask; the length is calculated based on the recorded + // number of conditions. + Expected Bitmask = Ctx.evaluateBitmask( + MCDCDecision->Mask, MCDCDecision->NumConditions); + if (auto E = Bitmask.takeError()) { + consumeError(std::move(E)); + return Error::success(); + } + + // Since the bitmask identifies the executed test vectors for an MC/DC + // DecisionRegion, all of the information is now available to process. + // This is where the bulk of the MC/DC progressing takes place. + Expected Record = + Ctx.evaluateMCDCRegion(*MCDCDecision, *Bitmask, MCDCBranches); + if (auto E = Record.takeError()) { + consumeError(std::move(E)); + return Error::success(); + } + + // Save the MC/DC Record so that it can be visualized later. + Function.pushMCDCRecord(*Record); + MCDCBranches.clear(); + } + } } // Don't create records for (filenames, function) pairs we've already seen. @@ -723,6 +991,10 @@ for (const auto &CR : Function.CountedBranchRegions) if (FileIDs.test(CR.FileID) && (CR.FileID == CR.ExpandedFileID)) FileCoverage.BranchRegions.push_back(CR); + // Capture MCDC records specific to the function. + for (const auto &MR : Function.MCDCRecords) + if (FileIDs.test(MR.getDecisionRegion().FileID)) + FileCoverage.MCDCRecords.push_back(MR); } LLVM_DEBUG(dbgs() << "Emitting segments for file: " << Filename << "\n"); @@ -775,6 +1047,11 @@ if (CR.FileID == *MainFileID) FunctionCoverage.BranchRegions.push_back(CR); + // Capture MCDC records specific to the function. + for (const auto &MR : Function.MCDCRecords) + if (MR.getDecisionRegion().FileID == *MainFileID) + FunctionCoverage.MCDCRecords.push_back(MR); + LLVM_DEBUG(dbgs() << "Emitting segments for function: " << Function.Name << "\n"); FunctionCoverage.Segments = SegmentBuilder::buildSegments(Regions); Index: llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp =================================================================== --- llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp +++ llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp @@ -237,6 +237,7 @@ unsigned LineStart = 0; for (size_t I = 0; I < NumRegions; ++I) { Counter C, C2; + uint64_t Mask = 0, Conditions = 0, ID = 0, TID = 0, FID = 0; CounterMappingRegion::RegionKind Kind = CounterMappingRegion::CodeRegion; // Read the combined counter + region kind. @@ -286,6 +287,28 @@ if (auto Err = readCounter(C2)) return Err; break; + case CounterMappingRegion::MCDCBranchRegion: + // For a MCDC Branch Region, read two successive counters and 3 IDs. + Kind = CounterMappingRegion::MCDCBranchRegion; + if (auto Err = readCounter(C)) + return Err; + if (auto Err = readCounter(C2)) + return Err; + if (auto Err = readIntMax(ID, std::numeric_limits::max())) + return Err; + if (auto Err = readIntMax(TID, std::numeric_limits::max())) + return Err; + if (auto Err = readIntMax(FID, std::numeric_limits::max())) + return Err; + break; + case CounterMappingRegion::MCDCDecisionRegion: + Kind = CounterMappingRegion::MCDCDecisionRegion; + if (auto Err = readIntMax(Mask, std::numeric_limits::max())) + return Err; + if (auto Err = + readIntMax(Conditions, std::numeric_limits::max())) + return Err; + break; default: return make_error(coveragemap_error::malformed); } @@ -337,9 +360,9 @@ dbgs() << "\n"; }); - auto CMR = CounterMappingRegion(C, C2, InferredFileID, ExpandedFileID, - LineStart, ColumnStart, - LineStart + NumLines, ColumnEnd, Kind); + auto CMR = CounterMappingRegion( + C, C2, Mask, Conditions, ID, TID, FID, InferredFileID, ExpandedFileID, + LineStart, ColumnStart, LineStart + NumLines, ColumnEnd, Kind); if (CMR.startLoc() > CMR.endLoc()) return make_error(coveragemap_error::malformed); MappingRegions.push_back(CMR); @@ -757,6 +780,7 @@ case CovMapVersion::Version4: case CovMapVersion::Version5: case CovMapVersion::Version6: + case CovMapVersion::Version7: // Decompress the name data. if (Error E = P.create(P.getNameData())) return std::move(E); @@ -775,6 +799,9 @@ else if (Version == CovMapVersion::Version6) return std::make_unique>(P, R, D, F); + else if (Version == CovMapVersion::Version7) + return std::make_unique>(P, R, D, F); } llvm_unreachable("Unsupported version"); } Index: llvm/lib/ProfileData/Coverage/CoverageMappingWriter.cpp =================================================================== --- llvm/lib/ProfileData/Coverage/CoverageMappingWriter.cpp +++ llvm/lib/ProfileData/Coverage/CoverageMappingWriter.cpp @@ -236,6 +236,23 @@ writeCounter(MinExpressions, Count, OS); writeCounter(MinExpressions, FalseCount, OS); break; + case CounterMappingRegion::MCDCBranchRegion: + encodeULEB128(unsigned(I->Kind) + << Counter::EncodingCounterTagAndExpansionRegionTagBits, + OS); + writeCounter(MinExpressions, Count, OS); + writeCounter(MinExpressions, FalseCount, OS); + encodeULEB128(unsigned(I->ID), OS); + encodeULEB128(unsigned(I->TrueID), OS); + encodeULEB128(unsigned(I->FalseID), OS); + break; + case CounterMappingRegion::MCDCDecisionRegion: + encodeULEB128(unsigned(I->Kind) + << Counter::EncodingCounterTagAndExpansionRegionTagBits, + OS); + encodeULEB128(unsigned(I->Mask), OS); + encodeULEB128(unsigned(I->NumConditions), OS); + break; } assert(I->LineStart >= PrevLineStart); encodeULEB128(I->LineStart - PrevLineStart, OS); Index: llvm/lib/ProfileData/InstrProf.cpp =================================================================== --- llvm/lib/ProfileData/InstrProf.cpp +++ llvm/lib/ProfileData/InstrProf.cpp @@ -133,6 +133,9 @@ case instrprof_error::count_mismatch: OS << "function basic block count change detected (counter mismatch)"; break; + case instrprof_error::bitmask_mismatch: + OS << "function bitmask size change detected (bitmask size mismatch)"; + break; case instrprof_error::counter_overflow: OS << "counter overflow"; break; @@ -152,6 +155,12 @@ OS << "profile uses zlib compression but the profile reader was built " "without zlib support"; break; + case instrprof_error::missing_section: + OS << "Missing profiling data section"; + break; + default: + llvm_unreachable("A value of instrprof_error has no message."); + break; } // If optional error message is not empty, append it to the message. @@ -745,6 +754,18 @@ Warn(instrprof_error::counter_overflow); } + // If the number of bitmask bytes doesn't match we either have bad data + // or a hash collision. + if (BitmaskBytes.size() != Other.BitmaskBytes.size()) { + Warn(instrprof_error::bitmask_mismatch); + return; + } + + // Bitmask bytes are merged by simply ORing them together. + for (size_t I = 0, E = Other.BitmaskBytes.size(); I < E; ++I) { + BitmaskBytes[I] = Other.BitmaskBytes[I] | BitmaskBytes[I]; + } + for (uint32_t Kind = IPVK_First; Kind <= IPVK_Last; ++Kind) mergeValueProfData(Kind, Other, Weight, Warn); } @@ -1371,9 +1392,10 @@ // When a new field is added in the header add a case statement here to // populate it. static_assert( - IndexedInstrProf::ProfVersion::CurrentVersion == Version8, + IndexedInstrProf::ProfVersion::CurrentVersion == Version9, "Please update the reading code below if a new field has been added, " "if not add a case statement to fall through to the latest version."); + case 9ull: case 8ull: H.MemProfOffset = read(Buffer, offsetOf(&Header::MemProfOffset)); [[fallthrough]]; @@ -1390,10 +1412,11 @@ // When a new field is added to the header add a case statement here to // compute the size as offset of the new field + size of the new field. This // relies on the field being added to the end of the list. - static_assert(IndexedInstrProf::ProfVersion::CurrentVersion == Version8, + static_assert(IndexedInstrProf::ProfVersion::CurrentVersion == Version9, "Please update the size computation below if a new field has " "been added to the header, if not add a case statement to " "fall through to the latest version."); + case 9ull: case 8ull: return offsetOf(&Header::MemProfOffset) + sizeof(Header::MemProfOffset); default: // Version7 (when the backwards compatible header was introduced). Index: llvm/lib/ProfileData/InstrProfCorrelator.cpp =================================================================== --- llvm/lib/ProfileData/InstrProfCorrelator.cpp +++ llvm/lib/ProfileData/InstrProfCorrelator.cpp @@ -192,11 +192,10 @@ } template -void InstrProfCorrelatorImpl::addProbe(StringRef FunctionName, - uint64_t CFGHash, - IntPtrT CounterOffset, - IntPtrT FunctionPtr, - uint32_t NumCounters) { +void InstrProfCorrelatorImpl::addProbe( + StringRef FunctionName, uint64_t CFGHash, IntPtrT CounterOffset, + IntPtrT BitmaskOffset, IntPtrT FunctionPtr, uint32_t NumCounters, + uint32_t NumBitmaskBytes) { // Check if a probe was already added for this counter offset. if (!CounterOffsets.insert(CounterOffset).second) return; @@ -206,11 +205,13 @@ // In this mode, CounterPtr actually stores the section relative address // of the counter. maybeSwap(CounterOffset), + maybeSwap(BitmaskOffset), maybeSwap(FunctionPtr), // TODO: Value profiling is not yet supported. /*ValuesPtr=*/maybeSwap(0), maybeSwap(NumCounters), /*NumValueSites=*/{maybeSwap(0), maybeSwap(0)}, + maybeSwap(NumBitmaskBytes), }); NamesVec.push_back(FunctionName.str()); } @@ -337,8 +338,8 @@ P.LineNumber = LineNumber; Data->Probes.push_back(P); } else { - this->addProbe(*FunctionName, *CFGHash, CounterOffset, - FunctionPtr.value_or(0), *NumCounters); + this->addProbe(*FunctionName, *CFGHash, CounterOffset, 0, + FunctionPtr.value_or(0), *NumCounters, /*Bitmaskbytes=*/0); } }; for (auto &CU : DICtx->normal_units()) Index: llvm/lib/ProfileData/InstrProfReader.cpp =================================================================== --- llvm/lib/ProfileData/InstrProfReader.cpp +++ llvm/lib/ProfileData/InstrProfReader.cpp @@ -306,6 +306,28 @@ Record.Counts.push_back(Count); } + // Bitmask byte information is indicated with special character. + if (Line->startswith("$")) { + Record.BitmaskBytes.clear(); + // Read the number of bitmask bytes. + uint64_t NumBitmaskBytes; + if ((Line++)->drop_front(1).getAsInteger(10, NumBitmaskBytes)) + return error(instrprof_error::malformed, + "number of bitmasks is not a valid integer"); + if (NumBitmaskBytes != 0) { + // Read each bitmask and fill our internal storage with the values. + Record.BitmaskBytes.reserve(NumBitmaskBytes); + for (uint8_t I = 0; I < NumBitmaskBytes; ++I) { + if (Line.is_at_end()) + return error(instrprof_error::truncated); + uint8_t BitmaskByte; + if ((Line++)->getAsInteger(16, BitmaskByte)) + return error(instrprof_error::malformed, "bitmask byte is invalid"); + Record.BitmaskBytes.push_back(BitmaskByte); + } + } + } + // Check if value profile data exists and read it if so. if (Error E = readValueProfileData(Record)) return error(std::move(E)); @@ -397,11 +419,14 @@ return error(instrprof_error::bad_header); CountersDelta = swap(Header.CountersDelta); + BitmaskDelta = swap(Header.BitmaskDelta); NamesDelta = swap(Header.NamesDelta); auto NumData = swap(Header.DataSize); auto PaddingBytesBeforeCounters = swap(Header.PaddingBytesBeforeCounters); auto CountersSize = swap(Header.CountersSize) * getCounterTypeSize(); auto PaddingBytesAfterCounters = swap(Header.PaddingBytesAfterCounters); + auto NumBitmaskBytes = swap(Header.NumBitmaskBytes); + auto PaddingBytesAfterBitmaskBytes = swap(Header.PaddingBytesAfterBitmaskBytes); auto NamesSize = swap(Header.NamesSize); ValueKindLast = swap(Header.ValueKindLast); @@ -411,8 +436,10 @@ // Profile data starts after profile header and binary ids if exist. ptrdiff_t DataOffset = sizeof(RawInstrProf::Header) + BinaryIdsSize; ptrdiff_t CountersOffset = DataOffset + DataSize + PaddingBytesBeforeCounters; + ptrdiff_t BitmaskOffset = CountersOffset + CountersSize + + PaddingBytesAfterCounters; ptrdiff_t NamesOffset = - CountersOffset + CountersSize + PaddingBytesAfterCounters; + BitmaskOffset + NumBitmaskBytes + PaddingBytesAfterBitmaskBytes; ptrdiff_t ValueDataOffset = NamesOffset + NamesSize + PaddingSize; auto *Start = reinterpret_cast(&Header); @@ -441,6 +468,8 @@ reinterpret_cast(&Header) + sizeof(RawInstrProf::Header); CountersStart = Start + CountersOffset; CountersEnd = CountersStart + CountersSize; + BitmaskStart = Start + BitmaskOffset; + BitmaskEnd = BitmaskStart + NumBitmaskBytes; ValueDataStart = reinterpret_cast(Start + ValueDataOffset); const uint8_t *BufferEnd = (const uint8_t *)DataBuffer->getBufferEnd(); @@ -514,6 +543,51 @@ return success(); } +template +Error RawInstrProfReader::readRawBitmaskBytes( + InstrProfRecord &Record) { + uint32_t NumBitmaskBytes = swap(Data->NumBitmaskBytes); + + Record.BitmaskBytes.clear(); + Record.BitmaskBytes.reserve(NumBitmaskBytes); + + // It's possible MCDC is either not enabled or only used for some functions + // and not others. So if we record 0 bytes, just move on. + if (NumBitmaskBytes == 0) { + return success(); + } + + // BitmaskDelta decreases as we advance to the next data record. + ptrdiff_t BitmaskOffset = swap(Data->BitmaskPtr) - BitmaskDelta; + if (BitmaskOffset < 0) + return error( + instrprof_error::malformed, + ("bitmask offset " + Twine(BitmaskOffset) + " is negative").str()); + + if (BitmaskOffset >= BitmaskEnd - BitmaskStart) + return error(instrprof_error::malformed, + ("bitmask offset " + Twine(BitmaskOffset) + + " is greater than the maximum bitmask offset " + + Twine(BitmaskEnd - BitmaskStart - 1)) + .str()); + + uint64_t MaxNumBitmaskBytes = + (BitmaskEnd - (BitmaskStart + BitmaskOffset)) / sizeof(uint8_t); + if (NumBitmaskBytes > MaxNumBitmaskBytes) + return error(instrprof_error::malformed, + ("number of bitmask bytes " + Twine(NumBitmaskBytes) + + " is greater than the maximum number of bitmask bytes " + + Twine(MaxNumBitmaskBytes)) + .str()); + + for (uint32_t I = 0; I < NumBitmaskBytes; I++) { + const char *Ptr = BitmaskStart + BitmaskOffset + I; + Record.BitmaskBytes.push_back(swap(*Ptr)); + } + + return success(); +} + template Error RawInstrProfReader::readValueProfilingData( InstrProfRecord &Record) { @@ -564,6 +638,10 @@ if (Error E = readRawCounts(Record)) return error(std::move(E)); + // Read raw bitmask bytes and set Record. + if (Error E = readRawBitmaskBytes(Record)) + return error(std::move(E)); + // Read value data and set Record. if (Error E = readValueProfilingData(Record)) return error(std::move(E)); @@ -662,6 +740,7 @@ DataBuffer.clear(); std::vector CounterBuffer; + std::vector BitmaskByteBuffer; const unsigned char *End = D + N; while (D < End) { @@ -687,7 +766,24 @@ for (uint64_t J = 0; J < CountsSize; ++J) CounterBuffer.push_back(endian::readNext(D)); - DataBuffer.emplace_back(K, Hash, std::move(CounterBuffer)); + // Read bitmask bytes for GET_VERSION(FormatVersion) > 8. + if (GET_VERSION(FormatVersion) > IndexedInstrProf::ProfVersion::Version8) { + uint64_t BitmaskBytes = 0; + if (D + sizeof(uint64_t) > End) + return data_type(); + BitmaskBytes = endian::readNext(D); + // Read bitmask byte values. + if (D + BitmaskBytes * sizeof(uint8_t) > End) + return data_type(); + BitmaskByteBuffer.clear(); + BitmaskByteBuffer.reserve(BitmaskBytes); + for (uint64_t J = 0; J < BitmaskBytes; ++J) + BitmaskByteBuffer.push_back(static_cast( + endian::readNext(D))); + } + + DataBuffer.emplace_back(K, Hash, std::move(CounterBuffer), + std::move(BitmaskByteBuffer)); // Read value profiling data. if (GET_VERSION(FormatVersion) > IndexedInstrProf::ProfVersion::Version2 && @@ -1121,6 +1217,16 @@ return success(); } +Error IndexedInstrProfReader::getFunctionBitmaskBytes( + StringRef FuncName, uint64_t FuncHash, std::vector &BitmaskBytes) { + Expected Record = getInstrProfRecord(FuncName, FuncHash); + if (Error E = Record.takeError()) + return error(std::move(E)); + + BitmaskBytes = Record.get().BitmaskBytes; + return success(); +} + Error IndexedInstrProfReader::readNextRecord(NamedInstrProfRecord &Record) { ArrayRef Data; Index: llvm/lib/ProfileData/InstrProfWriter.cpp =================================================================== --- llvm/lib/ProfileData/InstrProfWriter.cpp +++ llvm/lib/ProfileData/InstrProfWriter.cpp @@ -129,6 +129,8 @@ M += sizeof(uint64_t); // The function hash M += sizeof(uint64_t); // The size of the Counts vector M += ProfRecord.Counts.size() * sizeof(uint64_t); + M += sizeof(uint64_t); // The size of the Bitmask vector + M += ProfRecord.BitmaskBytes.size() * sizeof(uint64_t); // Value data M += ValueProfData::getSize(ProfileData.second); @@ -158,6 +160,10 @@ for (uint64_t I : ProfRecord.Counts) LE.write(I); + LE.write(ProfRecord.BitmaskBytes.size()); + for (uint64_t I : ProfRecord.BitmaskBytes) + LE.write(I); + // Write value data std::unique_ptr VDataPtr = ValueProfData::serializeFrom(ProfileData.second); @@ -307,6 +313,8 @@ const InstrProfRecord &IPR = Func.second; if (llvm::any_of(IPR.Counts, [](uint64_t Count) { return Count > 0; })) return true; + if (llvm::any_of(IPR.BitmaskBytes, [](uint8_t Byte) { return Byte > 0; })) + return true; } return false; } @@ -554,6 +562,16 @@ for (uint64_t Count : Func.Counts) OS << Count << "\n"; + if (Func.BitmaskBytes.size() > 0) { + OS << "# Num Bitmask Bytes:\n$" << Func.BitmaskBytes.size() << "\n"; + OS << "# Bitmask Byte Values:\n"; + for (uint8_t Byte : Func.BitmaskBytes) { + OS.write_hex(Byte); + OS << "\n"; + } + OS << "\n"; + } + uint32_t NumValueKinds = Func.getNumValueKinds(); if (!NumValueKinds) { OS << "\n"; Index: llvm/lib/Transforms/Instrumentation/InstrProfiling.cpp =================================================================== --- llvm/lib/Transforms/Instrumentation/InstrProfiling.cpp +++ llvm/lib/Transforms/Instrumentation/InstrProfiling.cpp @@ -427,6 +427,15 @@ } else if (auto *IPVP = dyn_cast(&Instr)) { lowerValueProfileInst(IPVP); MadeChange = true; + } else if (auto *IPMP = dyn_cast(&Instr)) { + IPMP->eraseFromParent(); + MadeChange = true; + } else if (auto *IPBU = dyn_cast(&Instr)) { + lowerMCDCBitmaskUpdate(IPBU); + MadeChange = true; + } else if (auto *IPTU = dyn_cast(&Instr)) { + lowerMCDCTempUpdate(IPTU); + MadeChange = true; } } } @@ -540,18 +549,42 @@ // the instrumented function. This is counting the number of instrumented // target value sites to enter it as field in the profile data variable. for (Function &F : M) { + InstrProfCoverInst *FirstProfCoverInst = nullptr; InstrProfIncrementInst *FirstProfIncInst = nullptr; - for (BasicBlock &BB : F) - for (auto I = BB.begin(), E = BB.end(); I != E; I++) + InstrProfMCDCParameters *FirstProfMCDCParameters = nullptr; + for (BasicBlock &BB : F) { + for (auto I = BB.begin(), E = BB.end(); I != E; I++) { if (auto *Ind = dyn_cast(I)) computeNumValueSiteCounts(Ind); - else if (FirstProfIncInst == nullptr) - FirstProfIncInst = dyn_cast(I); + else { + if (FirstProfCoverInst == nullptr) + FirstProfCoverInst = dyn_cast(I); + if (FirstProfIncInst == nullptr) + FirstProfIncInst = dyn_cast(I); + if (FirstProfMCDCParameters == nullptr) + FirstProfMCDCParameters = dyn_cast(I); + } + } + } + + // If the MCDCParameters intrinsic was seen, create the bitmasks. + if (FirstProfMCDCParameters != nullptr) { + static_cast(getOrCreateRegionBitmasks(FirstProfMCDCParameters)); + } - // Value profiling intrinsic lowering requires per-function profile data - // variable to be created first. + // If an Increment Intrinsic was seen, use that to create the region + // counters and data variable. Otherwise, use the Cover Intrinsic. Also + // create the data variable based on the MCDCParameters. + InstrProfInstBase *BaseInst = nullptr; if (FirstProfIncInst != nullptr) - static_cast(getOrCreateRegionCounters(FirstProfIncInst)); + BaseInst = FirstProfIncInst; + else + BaseInst = FirstProfCoverInst; + + if (BaseInst != nullptr) { + static_cast(getOrCreateRegionCounters(BaseInst)); + createDataVariable(BaseInst, FirstProfMCDCParameters); + } } for (Function &F : M) @@ -702,6 +735,19 @@ return Builder.CreateIntToPtr(Add, Addr->getType()); } +Value *InstrProfiling::getBitmaskAddress(InstrProfMCDCBitmaskUpdate *I) { + auto *Bitmasks = getOrCreateRegionBitmasks(I); + IRBuilder<> Builder(I); + + auto *Addr = + Builder.CreateConstInBoundsGEP2_32(Bitmasks->getValueType(), Bitmasks, 0, + I->getBitmaskIndex()->getZExtValue()); + + // TODO: May need to support runtime counter relocation for bitmasks prior to + // upstreaming. See getCounterAddress(). + return Addr; +} + void InstrProfiling::lowerCover(InstrProfCoverInst *CoverInstruction) { auto *Addr = getCounterAddress(CoverInstruction); IRBuilder<> Builder(CoverInstruction); @@ -746,6 +792,85 @@ CoverageNamesVar->eraseFromParent(); } +void InstrProfiling::lowerMCDCBitmaskUpdate( + InstrProfMCDCBitmaskUpdate *Update) { + IRBuilder<> Builder(Update); + auto *Int8Ty = Type::getInt8Ty(M->getContext()); + auto *Int8PtrTy = Type::getInt8PtrTy(M->getContext()); + auto *Int32Ty = Type::getInt32Ty(M->getContext()); + auto *Int64Ty = Type::getInt64Ty(M->getContext()); + auto *TempAddr = Update->getTempAddr(); + auto *BitmaskAddr = getBitmaskAddress(Update); + + // Load Temp Val. + // %mcdc.temp = load i32, ptr %mcdc.addr, align 4 + auto *Temp = Builder.CreateLoad(Int32Ty, TempAddr, "mcdc.temp"); + + // Calculate byte offset using div8. + // %1 = lshr i32 %mcdc.temp, 3 + auto *BitmaskByteOffset = Builder.CreateLShr(Temp, 0x3); + + // Add byte offset to section base byte address. + // %2 = zext i32 %1 to i64 + // %3 = add i64 ptrtoint (ptr @__profbm_test to i64), %2 + auto *BitmaskByteAddr = Builder.CreateAdd( + Builder.CreatePtrToInt(BitmaskAddr, Int64Ty), + Builder.CreateZExtOrBitCast(BitmaskByteOffset, Int64Ty)); + + // Convert to a pointer. + // %4 = inttoptr i32 %3 to ptr + BitmaskByteAddr = Builder.CreateIntToPtr(BitmaskByteAddr, Int8PtrTy); + + // Calculate bit offset into bitmask byte by using div8 remainder (AND ~8) + // %5 = and i32 %mcdc.temp, 7 + // %6 = trunc i32 %5 to i8 + auto *BitToSet = Builder.CreateTrunc(Builder.CreateAnd(Temp, 0x7), Int8Ty); + + // Shift bit offset left to form a bitmask. + // %7 = shl i8 1, %6 + auto *ShiftedVal = Builder.CreateShl(Builder.getInt8(0x1), BitToSet); + + // Load profile bitmask byte. + // %mcdc.bits = load i8, ptr %4, align 1 + auto *Bitmask = Builder.CreateLoad(Int8Ty, BitmaskByteAddr, "mcdc.bits"); + + // Perform logical OR of profile bitmask byte and shifted bit offset. + // %8 = or i8 %mcdc.bits, %7 + auto *Result = Builder.CreateOr(Bitmask, ShiftedVal); + + // Store the updated profile bitmask byte. + // store i8 %8, ptr %3, align 1 + Builder.CreateStore(Result, BitmaskByteAddr); + Update->eraseFromParent(); +} + +void InstrProfiling::lowerMCDCTempUpdate(InstrProfMCDCTempUpdate *Update) { + IRBuilder<> Builder(Update); + auto *Int32Ty = Type::getInt32Ty(M->getContext()); + auto *Addr = Update->getTempAddr(); + + // Load the MCDC temporary value from the stack. + // %mcdc.temp = load i32, ptr %mcdc.addr, align 4 + auto *Temp = Builder.CreateLoad(Int32Ty, Addr, "mcdc.temp"); + + // Zero-extend the evaluated condition boolean value (0 or 1) by 32bits. + // %1 = zext i1 %tobool to i32 + auto *CondV_32 = Builder.CreateZExt(Update->getCondBool(), Int32Ty); + + // Shift the boolean value left (by the condition's ID) to form a bitmask. + // %2 = shl i32 %1, getCondID()> + auto *ShiftedVal = Builder.CreateShl(CondV_32, Update->getCondID()); + + // Perform logical OR of the bitmask against the loaded MCDC temporary value. + // %3 = or i32 %mcdc.temp, %2 + auto *Result = Builder.CreateOr(Temp, ShiftedVal); + + // Store the updated temporary value back to the stack. + // store i32 %3, ptr %mcdc.addr, align 4 + Builder.CreateStore(Result, Addr); + Update->eraseFromParent(); +} + /// Get the name of a profiling variable for a particular function. static std::string getVarName(InstrProfInstBase *Inc, StringRef Prefix, bool &Renamed) { @@ -835,48 +960,42 @@ return true; } -GlobalVariable * -InstrProfiling::createRegionCounters(InstrProfInstBase *Inc, StringRef Name, - GlobalValue::LinkageTypes Linkage) { - uint64_t NumCounters = Inc->getNumCounters()->getZExtValue(); - auto &Ctx = M->getContext(); - GlobalVariable *GV; - if (isa(Inc)) { - auto *CounterTy = Type::getInt8Ty(Ctx); - auto *CounterArrTy = ArrayType::get(CounterTy, NumCounters); - // TODO: `Constant::getAllOnesValue()` does not yet accept an array type. - std::vector InitialValues(NumCounters, - Constant::getAllOnesValue(CounterTy)); - GV = new GlobalVariable(*M, CounterArrTy, false, Linkage, - ConstantArray::get(CounterArrTy, InitialValues), - Name); - GV->setAlignment(Align(1)); - } else { - auto *CounterTy = ArrayType::get(Type::getInt64Ty(Ctx), NumCounters); - GV = new GlobalVariable(*M, CounterTy, false, Linkage, - Constant::getNullValue(CounterTy), Name); - GV->setAlignment(Align(8)); +void InstrProfiling::maybeSetComdat(GlobalVariable *GV, Function *Fn, + StringRef VarName) { + bool DataReferencedByCode = profDataReferencedByCode(*M); + bool NeedComdat = needsComdatForCounter(*Fn, *M); + bool UseComdat = (NeedComdat || TT.isOSBinFormatELF()); + if (UseComdat) { + StringRef GroupName = TT.isOSBinFormatCOFF() && DataReferencedByCode + ? GV->getName() + : VarName; + Comdat *C = M->getOrInsertComdat(GroupName); + if (!NeedComdat) + C->setSelectionKind(Comdat::NoDeduplicate); + GV->setComdat(C); + // COFF doesn't allow the comdat group leader to have private linkage, so + // upgrade private linkage to internal linkage to produce a symbol table + // entry. + if (TT.isOSBinFormatCOFF() && GV->hasPrivateLinkage()) + GV->setLinkage(GlobalValue::InternalLinkage); } - return GV; } -GlobalVariable * -InstrProfiling::getOrCreateRegionCounters(InstrProfInstBase *Inc) { +GlobalVariable *InstrProfiling::setupProfileSection(InstrProfInstBase *Inc, + InstrProfSectKind IPSK) { + assert(IPSK == IPSK_cnts || IPSK == IPSK_bitmask); GlobalVariable *NamePtr = Inc->getName(); - auto &PD = ProfileDataMap[NamePtr]; - if (PD.RegionCounters) - return PD.RegionCounters; // Match the linkage and visibility of the name global. Function *Fn = Inc->getParent()->getParent(); GlobalValue::LinkageTypes Linkage = NamePtr->getLinkage(); GlobalValue::VisibilityTypes Visibility = NamePtr->getVisibility(); - // Use internal rather than private linkage so the counter variable shows up - // in the symbol table when using debug info for correlation. - if (DebugInfoCorrelate && TT.isOSBinFormatMachO() && - Linkage == GlobalValue::PrivateLinkage) - Linkage = GlobalValue::InternalLinkage; + // Use internal rather than private linkage so the counter variable shows up + // in the symbol table when using debug info for correlation. + if (DebugInfoCorrelate && TT.isOSBinFormatMachO() && + Linkage == GlobalValue::PrivateLinkage) + Linkage = GlobalValue::InternalLinkage; // Due to the limitation of binder as of 2021/09/28, the duplicate weak // symbols in the same csect won't be discarded. When there are duplicate weak @@ -904,42 +1023,83 @@ // nodeduplicate COMDAT which is lowered to a zero-flag section group. This // allows -z start-stop-gc to discard the entire group when the function is // discarded. - bool DataReferencedByCode = profDataReferencedByCode(*M); - bool NeedComdat = needsComdatForCounter(*Fn, *M); bool Renamed; - std::string CntsVarName = - getVarName(Inc, getInstrProfCountersVarPrefix(), Renamed); - std::string DataVarName = - getVarName(Inc, getInstrProfDataVarPrefix(), Renamed); - auto MaybeSetComdat = [&](GlobalVariable *GV) { - bool UseComdat = (NeedComdat || TT.isOSBinFormatELF()); - if (UseComdat) { - StringRef GroupName = TT.isOSBinFormatCOFF() && DataReferencedByCode - ? GV->getName() - : CntsVarName; - Comdat *C = M->getOrInsertComdat(GroupName); - if (!NeedComdat) - C->setSelectionKind(Comdat::NoDeduplicate); - GV->setComdat(C); - // COFF doesn't allow the comdat group leader to have private linkage, so - // upgrade private linkage to internal linkage to produce a symbol table - // entry. - if (TT.isOSBinFormatCOFF() && GV->hasPrivateLinkage()) - GV->setLinkage(GlobalValue::InternalLinkage); - } - }; + StringRef VarPrefix = (IPSK == IPSK_cnts) ? getInstrProfCountersVarPrefix() + : getInstrProfBitmaskVarPrefix(); + std::string VarName = getVarName(Inc, VarPrefix, Renamed); + + auto *Ptr = (IPSK == IPSK_cnts) ? createRegionCounters(Inc, VarName, Linkage) + : createRegionBitmasks(Inc, VarName, Linkage); + Ptr->setVisibility(Visibility); + // Put the counters and bitmask in their own sections so linkers can + // remove unneeded sections. + Ptr->setSection(getInstrProfSectionName(IPSK, TT.getObjectFormat())); + Ptr->setLinkage(Linkage); + maybeSetComdat(Ptr, Fn, VarName); + return Ptr; +} + +GlobalVariable * +InstrProfiling::createRegionBitmasks(InstrProfInstBase *Inc, StringRef Name, + GlobalValue::LinkageTypes Linkage) { + uint64_t NumBytes = Inc->getNumBitmaskBytes()->getZExtValue(); + auto *BitmaskTy = ArrayType::get(Type::getInt8Ty(M->getContext()), NumBytes); + auto GV = new GlobalVariable(*M, BitmaskTy, false, Linkage, + Constant::getNullValue(BitmaskTy), Name); + GV->setAlignment(Align(1)); + return GV; +} +GlobalVariable * +InstrProfiling::getOrCreateRegionBitmasks(InstrProfInstBase *Inc) { + GlobalVariable *NamePtr = Inc->getName(); + auto &PD = ProfileDataMap[NamePtr]; + if (PD.RegionBitmasks) + return PD.RegionBitmasks; + + auto *BitmaskPtr = setupProfileSection(Inc, IPSK_bitmask); + PD.RegionBitmasks = BitmaskPtr; + return PD.RegionBitmasks; +} + +GlobalVariable * +InstrProfiling::createRegionCounters(InstrProfInstBase *Inc, StringRef Name, + GlobalValue::LinkageTypes Linkage) { uint64_t NumCounters = Inc->getNumCounters()->getZExtValue(); - LLVMContext &Ctx = M->getContext(); + auto &Ctx = M->getContext(); + GlobalVariable *GV; + if (isa(Inc)) { + auto *CounterTy = Type::getInt8Ty(Ctx); + auto *CounterArrTy = ArrayType::get(CounterTy, NumCounters); + // TODO: `Constant::getAllOnesValue()` does not yet accept an array type. + std::vector InitialValues(NumCounters, + Constant::getAllOnesValue(CounterTy)); + GV = new GlobalVariable(*M, CounterArrTy, false, Linkage, + ConstantArray::get(CounterArrTy, InitialValues), + Name); + GV->setAlignment(Align(1)); + } else { + auto *CounterTy = ArrayType::get(Type::getInt64Ty(Ctx), NumCounters); + GV = new GlobalVariable(*M, CounterTy, false, Linkage, + Constant::getNullValue(CounterTy), Name); + GV->setAlignment(Align(8)); + } + return GV; +} + +GlobalVariable * +InstrProfiling::getOrCreateRegionCounters(InstrProfInstBase *Inc) { + GlobalVariable *NamePtr = Inc->getName(); + auto &PD = ProfileDataMap[NamePtr]; + if (PD.RegionCounters) + return PD.RegionCounters; - auto *CounterPtr = createRegionCounters(Inc, CntsVarName, Linkage); - CounterPtr->setVisibility(Visibility); - CounterPtr->setSection( - getInstrProfSectionName(IPSK_cnts, TT.getObjectFormat())); - CounterPtr->setLinkage(Linkage); - MaybeSetComdat(CounterPtr); + auto *CounterPtr = setupProfileSection(Inc, IPSK_cnts); PD.RegionCounters = CounterPtr; + if (DebugInfoCorrelate) { + LLVMContext &Ctx = M->getContext(); + Function *Fn = Inc->getParent()->getParent(); if (auto *SP = Fn->getSubprogram()) { DIBuilder DB(*M, true, SP->getUnit()); Metadata *FunctionNameAnnotation[] = { @@ -974,8 +1134,54 @@ Ctx.diagnose( DiagnosticInfoPGOProfile(M->getName().data(), Msg, DS_Warning)); } + + // Mark the counter variable as used so that it isn't optimized out. + CompilerUsedVars.push_back(PD.RegionCounters); + } + + return PD.RegionCounters; +} + +void InstrProfiling::createDataVariable(InstrProfInstBase *Inc, + InstrProfInstBase *Params) { + if (DebugInfoCorrelate) + return; + + GlobalVariable *NamePtr = Inc->getName(); + auto &PD = ProfileDataMap[NamePtr]; + + LLVMContext &Ctx = M->getContext(); + + Function *Fn = Inc->getParent()->getParent(); + GlobalValue::LinkageTypes Linkage = NamePtr->getLinkage(); + GlobalValue::VisibilityTypes Visibility = NamePtr->getVisibility(); + + // Use internal rather than private linkage so the counter variable shows up + // in the symbol table when using debug info for correlation. + if (DebugInfoCorrelate && TT.isOSBinFormatMachO() && + Linkage == GlobalValue::PrivateLinkage) + Linkage = GlobalValue::InternalLinkage; + + // Due to the limitation of binder as of 2021/09/28, the duplicate weak + // symbols in the same csect won't be discarded. When there are duplicate weak + // symbols, we can NOT guarantee that the relocations get resolved to the + // intended weak symbol, so we can not ensure the correctness of the relative + // CounterPtr, so we have to use private linkage for counter and data symbols. + if (TT.isOSBinFormatXCOFF()) { + Linkage = GlobalValue::PrivateLinkage; + Visibility = GlobalValue::DefaultVisibility; } + bool DataReferencedByCode = profDataReferencedByCode(*M); + bool NeedComdat = needsComdatForCounter(*Fn, *M); + bool Renamed; + + // The Data Variable section is anchored to profile counters. + std::string CntsVarName = + getVarName(Inc, getInstrProfCountersVarPrefix(), Renamed); + std::string DataVarName = + getVarName(Inc, getInstrProfDataVarPrefix(), Renamed); + auto *Int8PtrTy = Type::getInt8PtrTy(Ctx); // Allocate statically the array of pointers to value profile nodes for // the current function. @@ -993,16 +1199,17 @@ ValuesVar->setSection( getInstrProfSectionName(IPSK_vals, TT.getObjectFormat())); ValuesVar->setAlignment(Align(8)); - MaybeSetComdat(ValuesVar); + maybeSetComdat(ValuesVar, Fn, CntsVarName); ValuesPtrExpr = ConstantExpr::getBitCast(ValuesVar, Type::getInt8PtrTy(Ctx)); } - if (DebugInfoCorrelate) { - // Mark the counter variable as used so that it isn't optimized out. - CompilerUsedVars.push_back(PD.RegionCounters); - return PD.RegionCounters; - } + uint64_t NumCounters = Inc->getNumCounters()->getZExtValue(); + auto *CounterPtr = PD.RegionCounters; + + uint64_t NumBitmaskBytes = 0; + if (Params != nullptr) + NumBitmaskBytes = Params->getNumBitmaskBytes()->getZExtValue(); // Create data variable. auto *IntPtrTy = M->getDataLayout().getIntPtrType(M->getContext()); @@ -1047,6 +1254,16 @@ ConstantExpr::getSub(ConstantExpr::getPtrToInt(CounterPtr, IntPtrTy), ConstantExpr::getPtrToInt(Data, IntPtrTy)); + // Bitmasks are relative to the same data variable as profile counters. + GlobalVariable *BitmaskPtr = PD.RegionBitmasks; + Constant *RelativeBitmaskPtr = ConstantInt::get(IntPtrTy, 0); + + if (BitmaskPtr != nullptr) { + RelativeBitmaskPtr = + ConstantExpr::getSub(ConstantExpr::getPtrToInt(BitmaskPtr, IntPtrTy), + ConstantExpr::getPtrToInt(Data, IntPtrTy)); + } + Constant *DataVals[] = { #define INSTR_PROF_DATA(Type, LLVMType, Name, Init) Init, #include "llvm/ProfileData/InstrProfData.inc" @@ -1056,7 +1273,7 @@ Data->setVisibility(Visibility); Data->setSection(getInstrProfSectionName(IPSK_data, TT.getObjectFormat())); Data->setAlignment(Align(INSTR_PROF_DATA_ALIGNMENT)); - MaybeSetComdat(Data); + maybeSetComdat(Data, Fn, CntsVarName); PD.DataVar = Data; @@ -1068,8 +1285,6 @@ NamePtr->setLinkage(GlobalValue::PrivateLinkage); // Collect the referenced names to be used by emitNameData. ReferencedNames.push_back(NamePtr); - - return PD.RegionCounters; } void InstrProfiling::emitVNodes() { Index: llvm/test/Transforms/PGOProfile/comdat_internal.ll =================================================================== --- llvm/test/Transforms/PGOProfile/comdat_internal.ll +++ llvm/test/Transforms/PGOProfile/comdat_internal.ll @@ -13,9 +13,9 @@ ; CHECK: @__llvm_profile_raw_version = constant i64 {{[0-9]+}}, comdat ; CHECK-NOT: __profn__stdin__foo ; CHECK: @__profc__stdin__foo.[[#FOO_HASH]] = private global [1 x i64] zeroinitializer, section "__llvm_prf_cnts", comdat, align 8 -; CHECK: @__profd__stdin__foo.[[#FOO_HASH]] = private global { i64, i64, i64, ptr, ptr, i32, [2 x i16] } { i64 -5640069336071256030, i64 [[#FOO_HASH]], i64 sub (i64 ptrtoint (ptr @__profc__stdin__foo.742261418966908927 to i64), i64 ptrtoint (ptr @__profd__stdin__foo.742261418966908927 to i64)), ptr null +; CHECK: @__profd__stdin__foo.[[#FOO_HASH]] = private global { i64, i64, i64, i64, ptr, ptr, i32, [2 x i16], i32 } { i64 -5640069336071256030, i64 [[#FOO_HASH]], i64 sub (i64 ptrtoint (ptr @__profc__stdin__foo.742261418966908927 to i64), i64 ptrtoint (ptr @__profd__stdin__foo.742261418966908927 to i64)), i64 0, ptr null ; CHECK-NOT: @foo -; CHECK-SAME: , ptr null, i32 1, [2 x i16] zeroinitializer }, section "__llvm_prf_data", comdat($__profc__stdin__foo.[[#FOO_HASH]]), align 8 +; CHECK-SAME: , ptr null, i32 1, [2 x i16] zeroinitializer, i32 0 }, section "__llvm_prf_data", comdat($__profc__stdin__foo.[[#FOO_HASH]]), align 8 ; CHECK: @__llvm_prf_nm ; CHECK: @llvm.compiler.used Index: llvm/test/tools/llvm-cov/Inputs/binary-formats.canonical.json =================================================================== --- llvm/test/tools/llvm-cov/Inputs/binary-formats.canonical.json +++ llvm/test/tools/llvm-cov/Inputs/binary-formats.canonical.json @@ -4,6 +4,7 @@ CHECK-SAME: {"branches":[], CHECK-SAME: "expansions":[], CHECK-SAME: "filename":"/tmp/binary-formats.c", +CHECK-SAME: "mcdc_records":[], CHECK-SAME: "segments": CHECK-SAME: 4,40,100,true,true,false CHECK-SAME: 4,42,0,false,false,false @@ -11,11 +12,13 @@ CHECK-SAME: "functions":{"count":1,"covered":1,"percent":100}, CHECK-SAME: "instantiations":{"count":1,"covered":1,"percent":100}, CHECK-SAME: "lines":{"count":1,"covered":1,"percent":100}, +CHECK-SAME: "mcdc":{"count":0,"covered":0,"notcovered":0,"percent":0}, CHECK-SAME: "regions":{"count":1,"covered":1,"notcovered":0,"percent":100}}} CHECK-SAME: ], CHECK-SAME: "functions":[ CHECK-SAME: {"branches":[], -CHECK-SAME: "count":100,"filenames":["/tmp/binary-formats.c"],"name":"main", +CHECK-SAME: "count":100,"filenames":["/tmp/binary-formats.c"], +CHECK-SAME: "mcdc_records":[],"name":"main", CHECK-SAME: "regions": CHECK-SAME: 4,40,4,42,100,0,0,0 CHECK-SAME: } @@ -25,6 +28,7 @@ CHECK-SAME: "functions":{"count":1,"covered":1,"percent":100}, CHECK-SAME: "instantiations":{"count":1,"covered":1,"percent":100}, CHECK-SAME: "lines":{"count":1,"covered":1,"percent":100}, +CHECk-SAME: "mcdc":{"count":0,"covered":0,"notcovered":0,"percent":0}, CHECK-SAME: "regions":{"count":1,"covered":1,"notcovered":0,"percent":100}}} CHECK-SAME: ], CHECK-SAME: "type":"llvm.coverage.json.export" Index: llvm/test/tools/llvm-cov/Inputs/mcdc-const-folding.cpp =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/Inputs/mcdc-const-folding.cpp @@ -0,0 +1,117 @@ +#include + +bool case0(bool a) { + return 0 && a; +} +bool case1(bool a) { + return a && 0; +} +bool case2(bool a) { + return 1 && a; +} +bool case3(bool a) { + return a && 1; +} +bool case4(bool a) { + return 1 || a; +} +bool case5(bool a) { + return a || 1; +} +bool case6(bool a) { + return 0 || a; +} +bool case7(bool a) { + return a || 0; +} + +bool case8(bool a, bool b) { + return 0 && a && b; +} +bool case9(bool a, bool b) { + return a && 0 && b; +} +bool casea(bool a, bool b) { + return 1 && a && b; +} +bool caseb(bool a, bool b) { + return a && 1 && b; +} +bool casec(bool a, bool b) { + return 1 || a || b; +} +bool cased(bool a, bool b) { + return a || 1 || b; +} +bool casee(bool a, bool b) { + return 0 || a || b; +} +bool casef(bool a, bool b) { + return a || 0 || b; +} + +bool caseg(bool a, bool b) { + return b && a && 0; +} +bool caseh(bool a, bool b) { + return b && 0 && a; +} +bool casei(bool a, bool b) { + return b && a && 1; +} +bool casej(bool a, bool b) { + return b && 1 && a; +} +bool casek(bool a, bool b) { + return b || a || 1; +} +bool casel(bool a, bool b) { + return b || 1 || a; +} +bool casem(bool a, bool b) { + return b || a || 0; +} +bool casen(bool a, bool b) { + return b || 0 || a; +} + +extern "C" { + extern void __llvm_profile_write_file(void); +} + +int main(int argc, char *argv[]) +{ + bool a = atoi(argv[1]); + bool b = atoi(argv[2]); + volatile bool c; + + c = case0(a); + c = case1(a); + c = case2(a); + c = case3(a); + c = case4(a); + c = case5(a); + c = case6(a); + c = case7(a); + + c = case8(a, b); + c = case9(a, b); + c = casea(a, b); + c = caseb(a, b); + c = casec(a, b); + c = cased(a, b); + c = casee(a, b); + c = casef(a, b); + + c = caseg(a, b); + c = caseh(a, b); + c = casei(a, b); + c = casej(a, b); + c = casek(a, b); + c = casel(a, b); + c = casem(a, b); + c = casen(a, b); + + __llvm_profile_write_file(); + return 0; +} Index: llvm/test/tools/llvm-cov/Inputs/mcdc-const-folding.proftext =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/Inputs/mcdc-const-folding.proftext @@ -0,0 +1,400 @@ +_Z5case8bb +# Func Hash: +99214 +# Num Counters: +5 +# Counter Values: +4 +0 +0 +0 +0 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +1 + + +_Z5case5b +# Func Hash: +1551 +# Num Counters: +3 +# Counter Values: +4 +1 +0 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +6 + + +_Z5caseabb +# Func Hash: +99214 +# Num Counters: +5 +# Counter Values: +4 +3 +2 +4 +3 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +a2 + + +_Z5case6b +# Func Hash: +1551 +# Num Counters: +3 +# Counter Values: +4 +4 +1 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +5 + + +_Z5casegbb +# Func Hash: +99214 +# Num Counters: +5 +# Counter Values: +4 +2 +0 +3 +2 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +23 + + +_Z5case1b +# Func Hash: +1550 +# Num Counters: +3 +# Counter Values: +4 +3 +0 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +3 + + +_Z5case7b +# Func Hash: +1551 +# Num Counters: +3 +# Counter Values: +4 +1 +1 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +3 + + +_Z5casedbb +# Func Hash: +99279 +# Num Counters: +5 +# Counter Values: +4 +0 +0 +1 +0 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +12 + + +_Z5casekbb +# Func Hash: +99279 +# Num Counters: +5 +# Counter Values: +4 +0 +0 +1 +0 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +12 + + +_Z5casehbb +# Func Hash: +99214 +# Num Counters: +5 +# Counter Values: +4 +0 +0 +3 +0 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +3 + + +_Z5case4b +# Func Hash: +1551 +# Num Counters: +3 +# Counter Values: +4 +0 +0 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +2 + + +_Z5caseibb +# Func Hash: +99214 +# Num Counters: +5 +# Counter Values: +4 +2 +2 +3 +2 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +83 + + +_Z5case2b +# Func Hash: +1550 +# Num Counters: +3 +# Counter Values: +4 +4 +3 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +a + + +_Z5casefbb +# Func Hash: +99279 +# Num Counters: +5 +# Counter Values: +4 +1 +0 +1 +1 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +6 + + +_Z5caselbb +# Func Hash: +99279 +# Num Counters: +5 +# Counter Values: +4 +0 +0 +1 +0 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +12 + + +_Z5casenbb +# Func Hash: +99279 +# Num Counters: +5 +# Counter Values: +4 +1 +0 +1 +1 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +6 + + +_Z5case9bb +# Func Hash: +99214 +# Num Counters: +5 +# Counter Values: +4 +0 +0 +3 +0 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +3 + + +_Z5casecbb +# Func Hash: +99279 +# Num Counters: +5 +# Counter Values: +4 +0 +0 +0 +0 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +2 + + +_Z5casebbb +# Func Hash: +99214 +# Num Counters: +5 +# Counter Values: +4 +3 +2 +3 +3 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +a1 + + +_Z5case0b +# Func Hash: +1550 +# Num Counters: +3 +# Counter Values: +4 +0 +0 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +1 + + +_Z5casejbb +# Func Hash: +99214 +# Num Counters: +5 +# Counter Values: +4 +3 +2 +3 +3 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +a1 + + +_Z5caseebb +# Func Hash: +99279 +# Num Counters: +5 +# Counter Values: +4 +1 +0 +4 +1 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +14 + + +main +# Func Hash: +24 +# Num Counters: +1 +# Counter Values: +4 + +_Z5case3b +# Func Hash: +1550 +# Num Counters: +3 +# Counter Values: +4 +3 +3 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +9 + + +_Z5casembb +# Func Hash: +99279 +# Num Counters: +5 +# Counter Values: +4 +0 +0 +1 +0 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +12 + + Index: llvm/test/tools/llvm-cov/Inputs/mcdc-const.cpp =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/Inputs/mcdc-const.cpp @@ -0,0 +1,27 @@ + +#include + +extern "C" { +extern void __llvm_profile_write_file(void); +} + +extern int foo(); + +void test(bool a, bool b, bool c, bool d) { + + if ((a && 1) || (0 && d) || 0) + printf("test1 decision true\n"); +} + +int main() +{ + test(true,false,true,false); + test(true,false,true,true); + test(true,true,false,false); + test(false,true,true,false); + + test(true,false,false,false); + + __llvm_profile_write_file(); + return 0; +} Index: llvm/test/tools/llvm-cov/Inputs/mcdc-const.proftext =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/Inputs/mcdc-const.proftext @@ -0,0 +1,32 @@ +main +# Func Hash: +24 +# Num Counters: +1 +# Counter Values: +1 + +_Z4testbbbb +# Func Hash: +703556281489 +# Num Counters: +9 +# Counter Values: +5 +4 +1 +1 +1 +4 +4 +0 +0 +# Num Bitmask Bytes: +$4 +# Bitmask Byte Values: +1 +2 +0 +0 + + Index: llvm/test/tools/llvm-cov/Inputs/mcdc-general-none.proftext =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/Inputs/mcdc-general-none.proftext @@ -0,0 +1,44 @@ +main +# Func Hash: +24 +# Num Counters: +1 +# Counter Values: +1 + +_Z4testbbbb +# Func Hash: +9819241276358969079 +# Num Counters: +19 +# Counter Values: +7 +3 +5 +5 +2 +3 +1 +2 +3 +2 +1 +1 +1 +1 +2 +4 +2 +2 +1 +# Num Bitmask Bytes: +$6 +# Bitmask Byte Values: +0 +0 +0 +0 +0 +0 + + Index: llvm/test/tools/llvm-cov/Inputs/mcdc-general.cpp =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/Inputs/mcdc-general.cpp @@ -0,0 +1,36 @@ + +#include + +extern "C" { +extern void __llvm_profile_write_file(void); +} + +extern int foo(); + +void test(bool a, bool b, bool c, bool d) { + + if ((a && b) || (c && d)) + printf("test1 decision true\n"); + + if (b && c) if (a && d) + printf("test2 decision true\n"); + + if ((c && d) && + (a && b)) + printf("test3 decision true\n"); +} + +int main() +{ + test(false,false,false,false); + test(true,false,true,false); + test(true,false,true,true); + test(true,true,false,false); + + test(true,false,false,false); + test(true,true,true,true); + test(false,true,true,false); + + __llvm_profile_write_file(); + return 0; +} Index: llvm/test/tools/llvm-cov/Inputs/mcdc-general.proftext =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/Inputs/mcdc-general.proftext @@ -0,0 +1,44 @@ +main +# Func Hash: +24 +# Num Counters: +1 +# Counter Values: +1 + +_Z4testbbbb +# Func Hash: +9819241276358969079 +# Num Counters: +19 +# Counter Values: +7 +3 +5 +5 +2 +3 +1 +2 +3 +2 +1 +1 +1 +1 +2 +4 +2 +2 +1 +# Num Bitmask Bytes: +$6 +# Bitmask Byte Values: +2f +8 +b +9 +83 +80 + + Index: llvm/test/tools/llvm-cov/mcdc-const.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/mcdc-const.test @@ -0,0 +1,203 @@ +// Test visualization of MC/DC constructs for constant-folded condition masking. + +// RUN: llvm-profdata merge %S/Inputs/mcdc-const.proftext -o %t.profdata +// RUN: llvm-cov show --show-branches=count --show-mcdc %S/Inputs/mcdc-const.o32l -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-const.cpp | FileCheck %s -check-prefix=CHECKGENERALCASE + +// CHECKGENERALCASE: ------------------ +// CHECKGENERALCASE-NEXT: | Branch (12:8): [True: 4, False: 1] +// CHECKGENERALCASE-NEXT: | Branch (12:13): [Folded - Ignored] +// CHECKGENERALCASE-NEXT: | Branch (12:20): [Folded - Ignored] +// CHECKGENERALCASE-NEXT: | Branch (12:25): [True: 0, False: 0] +// CHECKGENERALCASE-NEXT: | Branch (12:31): [Folded - Ignored] +// CHECKGENERALCASE-NEXT: ------------------ +// CHECKGENERALCASE-NEXT: |---> MC/DC Decision Region (12:7) to (12:32) +// CHECKGENERALCASE-NEXT: | +// CHECKGENERALCASE-NEXT: | Number of Conditions: 5 +// CHECKGENERALCASE-NEXT: | Condition C1 --> (12:8) +// CHECKGENERALCASE-NEXT: | Condition C2 --> (12:13) +// CHECKGENERALCASE-NEXT: | Condition C3 --> (12:20) +// CHECKGENERALCASE-NEXT: | Condition C4 --> (12:25) +// CHECKGENERALCASE-NEXT: | Condition C5 --> (12:31) +// CHECKGENERALCASE-NEXT: | +// CHECKGENERALCASE-NEXT: | Executed MC/DC Test Vectors: +// CHECKGENERALCASE-NEXT: | +// CHECKGENERALCASE-NEXT: | C1, C2, C3, C4, C5 Result +// CHECKGENERALCASE-NEXT: | 1 { F, C, C, -, C = F } +// CHECKGENERALCASE-NEXT: | 2 { T, C, C, -, C = T } +// CHECKGENERALCASE-NEXT: | +// CHECKGENERALCASE-NEXT: | C1-Pair: covered: (1,2) +// CHECKGENERALCASE-NEXT: | C2-Pair: constant folded +// CHECKGENERALCASE-NEXT: | C3-Pair: constant folded +// CHECKGENERALCASE-NEXT: | C4-Pair: not covered +// CHECKGENERALCASE-NEXT: | C5-Pair: constant folded +// CHECKGENERALCASE-NEXT: | MC/DC Coverage for Decision: 50.00% +// CHECKGENERALCASE-NEXT: | +// CHECKGENERALCASE-NEXT: ------------------ + +// RUN: llvm-profdata merge %S/Inputs/mcdc-const-folding.proftext -o %t.profdata +// RUN: llvm-cov show --show-mcdc %S/Inputs/mcdc-const-folding.o32l -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-const-folding.cpp | FileCheck %s -check-prefix=CHECKFULLCASE +// RUN: llvm-cov report --show-mcdc-summary %S/Inputs/mcdc-const-folding.o32l -instr-profile %t.profdata -show-functions -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-const-folding.cpp | FileCheck %s -check-prefix=REPORT + +// CHECKFULLCASE: | 1 { C, - = F } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE: | C2-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { F, C = F } +// CHECKFULLCASE: | 2 { T, C = F } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C2-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { C, F = F } +// CHECKFULLCASE: | 2 { C, T = T } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE: | C2-Pair: covered: (1,2) +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { F, C = F } +// CHECKFULLCASE: | 2 { T, C = T } +// CHECKFULLCASE: | C1-Pair: covered: (1,2) +// CHECKFULLCASE: | C2-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { C, - = T } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE: | C2-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { T, C = T } +// CHECKFULLCASE: | 2 { F, C = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C2-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { C, F = F } +// CHECKFULLCASE: | 2 { C, T = T } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE: | C2-Pair: covered: (1,2) +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { F, C = F } +// CHECKFULLCASE: | 2 { T, C = T } +// CHECKFULLCASE: | C1-Pair: covered: (1,2) +// CHECKFULLCASE: | C2-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { C, -, - = F } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE: | C2-Pair: not covered +// CHECKFULLCASE: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { F, C, - = F } +// CHECKFULLCASE: | 2 { T, C, - = F } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C2-Pair: constant folded +// CHECKFULLCASE: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { C, F, - = F } +// CHECKFULLCASE: | 2 { C, T, F = F } +// CHECKFULLCASE: | 3 { C, T, T = T } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE: | C2-Pair: covered: (1,3) +// CHECKFULLCASE: | C3-Pair: covered: (2,3) +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { F, C, - = F } +// CHECKFULLCASE: | 2 { T, C, F = F } +// CHECKFULLCASE: | 3 { T, C, T = T } +// CHECKFULLCASE: | C1-Pair: covered: (1,3) +// CHECKFULLCASE: | C2-Pair: constant folded +// CHECKFULLCASE: | C3-Pair: covered: (2,3) +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { C, -, - = T } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE: | C2-Pair: not covered +// CHECKFULLCASE: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { T, C, - = T } +// CHECKFULLCASE: | 2 { F, C, - = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C2-Pair: constant folded +// CHECKFULLCASE: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { C, F, T = T } +// CHECKFULLCASE: | 2 { C, T, - = T } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE: | C2-Pair: not covered +// CHECKFULLCASE: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { T, C, - = T } +// CHECKFULLCASE: | 2 { F, C, T = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C2-Pair: constant folded +// CHECKFULLCASE: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { F, -, C = F } +// CHECKFULLCASE: | 2 { T, F, C = F } +// CHECKFULLCASE: | 3 { T, T, C = F } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C2-Pair: not covered +// CHECKFULLCASE: | C3-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { F, C, - = F } +// CHECKFULLCASE: | 2 { T, C, - = F } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C2-Pair: constant folded +// CHECKFULLCASE: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { F, -, C = F } +// CHECKFULLCASE: | 2 { T, F, C = F } +// CHECKFULLCASE: | 3 { T, T, C = T } +// CHECKFULLCASE: | C1-Pair: covered: (1,3) +// CHECKFULLCASE: | C2-Pair: covered: (2,3) +// CHECKFULLCASE: | C3-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { F, C, - = F } +// CHECKFULLCASE: | 2 { T, C, F = F } +// CHECKFULLCASE: | 3 { T, C, T = T } +// CHECKFULLCASE: | C1-Pair: covered: (1,3) +// CHECKFULLCASE: | C2-Pair: constant folded +// CHECKFULLCASE: | C3-Pair: covered: (2,3) +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { T, -, C = T } +// CHECKFULLCASE: | 2 { F, T, C = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C2-Pair: not covered +// CHECKFULLCASE: | C3-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { T, C, - = T } +// CHECKFULLCASE: | 2 { F, C, - = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C2-Pair: constant folded +// CHECKFULLCASE: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { T, -, C = T } +// CHECKFULLCASE: | 2 { F, T, C = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C2-Pair: not covered +// CHECKFULLCASE: | C3-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { T, C, - = T } +// CHECKFULLCASE: | 2 { F, C, T = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C2-Pair: constant folded +// CHECKFULLCASE: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% + +// REPORT: _Z5case0b {{.*}} 1 1 0.00% +// REPORT: _Z5case1b {{.*}} 1 1 0.00% +// REPORT: _Z5case2b {{.*}} 1 0 100.00% +// REPORT: _Z5case3b {{.*}} 1 0 100.00% +// REPORT: _Z5case4b {{.*}} 1 1 0.00% +// REPORT: _Z5case5b {{.*}} 1 1 0.00% +// REPORT: _Z5case6b {{.*}} 1 0 100.00% +// REPORT: _Z5case7b {{.*}} 1 0 100.00% +// REPORT: _Z5case8bb {{.*}} 2 2 0.00% +// REPORT: _Z5case9bb {{.*}} 2 2 0.00% +// REPORT: _Z5caseabb {{.*}} 2 0 100.00% +// REPORT: _Z5casebbb {{.*}} 2 0 100.00% +// REPORT: _Z5casecbb {{.*}} 2 2 0.00% +// REPORT: _Z5casedbb {{.*}} 2 2 0.00% +// REPORT: _Z5caseebb {{.*}} 2 2 0.00% +// REPORT: _Z5casefbb {{.*}} 2 2 0.00% +// REPORT: _Z5casegbb {{.*}} 2 2 0.00% +// REPORT: _Z5casehbb {{.*}} 2 2 0.00% +// REPORT: _Z5caseibb {{.*}} 2 0 100.00% +// REPORT: _Z5casejbb {{.*}} 2 0 100.00% +// REPORT: _Z5casekbb {{.*}} 2 2 0.00% +// REPORT: _Z5caselbb {{.*}} 2 2 0.00% +// REPORT: _Z5casembb {{.*}} 2 2 0.00% +// REPORT: _Z5casenbb {{.*}} 2 2 0.00% +// REPORT: TOTAL {{.*}} 40 28 30.00% Index: llvm/test/tools/llvm-cov/mcdc-export-json.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/mcdc-export-json.test @@ -0,0 +1,8 @@ +// RUN: llvm-profdata merge %S/Inputs/mcdc-general.proftext -o %t.profdata +// RUN: llvm-cov export --format=text %S/Inputs/mcdc-general.o32l -instr-profile %t.profdata | FileCheck %s + +// CHECK: 12,7,12,27,0,5,[true,true,true,true] +// CHECK: 15,7,15,13,0,5,[true,true] +// CHECK: 15,19,15,25,0,5,[true,false] +// CHECK: 18,7,19,15,0,5,[true,true,false,true] +// CHECK: "mcdc":{"count":12,"covered":10,"notcovered":2,"percent":83.333333333333343} Index: llvm/test/tools/llvm-cov/mcdc-general-none.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/mcdc-general-none.test @@ -0,0 +1,72 @@ +// Test visualization of general MC/DC constructs with 0 executed test vectors. + +// RUN: llvm-profdata merge %S/Inputs/mcdc-general-none.proftext -o %t.profdata +// RUN: llvm-cov show --show-mcdc %S/Inputs/mcdc-general.o32l -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s +// RUN: llvm-cov report --show-mcdc-summary %S/Inputs/mcdc-general.o32l -instr-profile %t.profdata -show-functions -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=REPORT + +// CHECK: test(bool + +// CHECK: ------------------ +// CHECK-NEXT: |---> MC/DC Decision Region (12:7) to (12:27) +// CHECK-NEXT: | +// CHECK-NEXT: | Number of Conditions: 4 +// CHECK-NEXT: | Condition C1 --> (12:8) +// CHECK-NEXT: | Condition C2 --> (12:13) +// CHECK-NEXT: | Condition C3 --> (12:20) +// CHECK-NEXT: | Condition C4 --> (12:25) +// CHECK-NEXT: | +// CHECK-NEXT: | Executed MC/DC Test Vectors: +// CHECK-NEXT: | +// CHECK-NEXT: | None. +// CHECK-NEXT: | +// CHECK-NEXT: | C1-Pair: not covered +// CHECK-NEXT: | C2-Pair: not covered +// CHECK-NEXT: | C3-Pair: not covered +// CHECK-NEXT: | C4-Pair: not covered +// CHECK-NEXT: | MC/DC Coverage for Decision: 0.00% +// CHECK-NEXT: | +// CHECK-NEXT: ------------------ + + +// Turn off MC/DC visualization. +// RUN: llvm-cov show %S/Inputs/mcdc-general.o32l -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=NOMCDC +// NOMCDC-NOT: MC/DC Decision Region + +// REPORT: Name Regions Miss Cover Lines Miss Cover Branches Miss Cover MC/DC Conditions Miss Cover +// REPORT-NEXT: ------------------------------------------------------------------------------------------------------------------------------------------- +// REPORT-NEXT: _Z4testbbbb 25 0 100.00% 9 0 100.00% 24 2 91.67% 12 12 0.00% +// REPORT-NEXT: main 1 0 100.00% 11 0 100.00% 0 0 0.00% 0 0 0.00% +// REPORT-NEXT: --- +// REPORT-NEXT: TOTAL 26 0 100.00% 20 0 100.00% 24 2 91.67% 12 12 0.00% + +// Turn off MC/DC summary. +// RUN: llvm-cov report %S/Inputs/mcdc-general.o32l -instr-profile %t.profdata -show-functions -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=REPORT_NOMCDC +// REPORT_NOMCDC-NOT: TOTAL{{.*}}12 12 0.00% + + +// Test file-level report. +// RUN: llvm-cov report --show-mcdc-summary %S/Inputs/mcdc-general.o32l -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=FILEREPORT +// FILEREPORT: TOTAL{{.*}}12 12 0.00% + + +// Test html output. +// RUN: llvm-cov show --show-mcdc-summary --show-mcdc %S/Inputs/mcdc-general.o32l -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp -format html -o %t.html.dir +// RUN: FileCheck -check-prefix=HTML -input-file=%t.html.dir/coverage/tmp/mcdc-general.cpp.html %s +// HTML-COUNT-4: MC/DC Decision Region ( + +// RUN: FileCheck -check-prefix HTML-INDEX -input-file %t.html.dir/index.html %s +// HTML-INDEX-LABEL: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX:
FilenameFunction CoverageLine CoverageRegion CoverageBranch CoverageMC/DC +// HTML-INDEX: 100.00% (2/2) +// HTML-INDEX: 100.00% (20/20) +// HTML-INDEX: 100.00% (26/26) +// HTML-INDEX: 91.67% (22/24) +// HTML-INDEX: 0.00% (0/12) +// HTML-INDEX: Totals Index: llvm/test/tools/llvm-cov/mcdc-general.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/mcdc-general.test @@ -0,0 +1,138 @@ +// Test visualization of general MC/DC constructs. + +// RUN: llvm-profdata merge %S/Inputs/mcdc-general.proftext -o %t.profdata +// RUN: llvm-cov show --show-mcdc %S/Inputs/mcdc-general.o32l -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s +// RUN: llvm-cov report --show-mcdc-summary %S/Inputs/mcdc-general.o32l -instr-profile %t.profdata -show-functions -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=REPORT + +// CHECK: test(bool + +// CHECK: ------------------ +// CHECK-NEXT: |---> MC/DC Decision Region (12:7) to (12:27) +// CHECK-NEXT: | +// CHECK-NEXT: | Number of Conditions: 4 +// CHECK-NEXT: | Condition C1 --> (12:8) +// CHECK-NEXT: | Condition C2 --> (12:13) +// CHECK-NEXT: | Condition C3 --> (12:20) +// CHECK-NEXT: | Condition C4 --> (12:25) +// CHECK-NEXT: | +// CHECK-NEXT: | Executed MC/DC Test Vectors: +// CHECK-NEXT: | +// CHECK-NEXT: | C1, C2, C3, C4 Result +// CHECK-NEXT: | 1 { F, -, F, - = F } +// CHECK-NEXT: | 2 { T, F, F, - = F } +// CHECK-NEXT: | 3 { F, -, T, F = F } +// CHECK-NEXT: | 4 { T, F, T, F = F } +// CHECK-NEXT: | 5 { T, T, -, - = T } +// CHECK-NEXT: | 6 { T, F, T, T = T } +// CHECK-NEXT: | +// CHECK-NEXT: | C1-Pair: covered: (1,5) +// CHECK-NEXT: | C2-Pair: covered: (2,5) +// CHECK-NEXT: | C3-Pair: covered: (2,6) +// CHECK-NEXT: | C4-Pair: covered: (4,6) +// CHECK-NEXT: | MC/DC Coverage for Decision: 100.00% +// CHECK-NEXT: | +// CHECK-NEXT: ------------------ + +// CHECK: ------------------ +// CHECK-NEXT: |---> MC/DC Decision Region (15:7) to (15:13) +// CHECK-NEXT: | +// CHECK-NEXT: | Number of Conditions: 2 +// CHECK-NEXT: | Condition C1 --> (15:7) +// CHECK-NEXT: | Condition C2 --> (15:12) +// CHECK-NEXT: | +// CHECK-NEXT: | Executed MC/DC Test Vectors: +// CHECK-NEXT: | +// CHECK-NEXT: | C1, C2 Result +// CHECK-NEXT: | 1 { F, - = F } +// CHECK-NEXT: | 2 { T, F = F } +// CHECK-NEXT: | 3 { T, T = T } +// CHECK-NEXT: | +// CHECK-NEXT: | C1-Pair: covered: (1,3) +// CHECK-NEXT: | C2-Pair: covered: (2,3) +// CHECK-NEXT: | MC/DC Coverage for Decision: 100.00% +// CHECK-NEXT: | +// CHECK-NEXT: |---> MC/DC Decision Region (15:19) to (15:25) +// CHECK-NEXT: | +// CHECK-NEXT: | Number of Conditions: 2 +// CHECK-NEXT: | Condition C1 --> (15:19) +// CHECK-NEXT: | Condition C2 --> (15:24) +// CHECK-NEXT: | +// CHECK-NEXT: | Executed MC/DC Test Vectors: +// CHECK-NEXT: | +// CHECK-NEXT: | C1, C2 Result +// CHECK-NEXT: | 1 { F, - = F } +// CHECK-NEXT: | 2 { T, T = T } +// CHECK-NEXT: | +// CHECK-NEXT: | C1-Pair: covered: (1,2) +// CHECK-NEXT: | C2-Pair: not covered +// CHECK-NEXT: | MC/DC Coverage for Decision: 50.00% +// CHECK-NEXT: | +// CHECK-NEXT: ------------------ + +// CHECK: ------------------ +// CHECK-NEXT: |---> MC/DC Decision Region (18:7) to (19:15) +// CHECK-NEXT: | +// CHECK-NEXT: | Number of Conditions: 4 +// CHECK-NEXT: | Condition C1 --> (18:8) +// CHECK-NEXT: | Condition C2 --> (18:13) +// CHECK-NEXT: | Condition C3 --> (19:8) +// CHECK-NEXT: | Condition C4 --> (19:13) +// CHECK-NEXT: | +// CHECK-NEXT: | Executed MC/DC Test Vectors: +// CHECK-NEXT: | +// CHECK-NEXT: | C1, C2, C3, C4 Result +// CHECK-NEXT: | 1 { F, -, -, - = F } +// CHECK-NEXT: | 2 { T, F, -, - = F } +// CHECK-NEXT: | 3 { T, T, T, F = F } +// CHECK-NEXT: | 4 { T, T, T, T = T } +// CHECK-NEXT: | +// CHECK-NEXT: | C1-Pair: covered: (1,4) +// CHECK-NEXT: | C2-Pair: covered: (2,4) +// CHECK-NEXT: | C3-Pair: not covered +// CHECK-NEXT: | C4-Pair: covered: (3,4) +// CHECK-NEXT: | MC/DC Coverage for Decision: 75.00% +// CHECK-NEXT: | +// CHECK-NEXT: ------------------ + +// Turn off MC/DC visualization. +// RUN: llvm-cov show %S/Inputs/mcdc-general.o32l -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=NOMCDC +// NOMCDC-NOT: MC/DC Decision Region + +// REPORT: Name Regions Miss Cover Lines Miss Cover Branches Miss Cover MC/DC Conditions Miss Cover +// REPORT-NEXT: ------------------------------------------------------------------------------------------------------------------------------------------- +// REPORT-NEXT: _Z4testbbbb 25 0 100.00% 9 0 100.00% 24 2 91.67% 12 2 83.33% +// REPORT-NEXT: main 1 0 100.00% 11 0 100.00% 0 0 0.00% 0 0 0.00% +// REPORT-NEXT: --- +// REPORT-NEXT: TOTAL 26 0 100.00% 20 0 100.00% 24 2 91.67% 12 2 83.33% + +// Turn off MC/DC summary. +// RUN: llvm-cov report %S/Inputs/mcdc-general.o32l -instr-profile %t.profdata -show-functions -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=REPORT_NOMCDC +// REPORT_NOMCDC-NOT: TOTAL{{.*}}12 2 83.33% + + +// Test file-level report. +// RUN: llvm-cov report --show-mcdc-summary %S/Inputs/mcdc-general.o32l -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=FILEREPORT +// FILEREPORT: TOTAL{{.*}}12 2 83.33% + + +// Test html output. +// RUN: llvm-cov show --show-mcdc-summary --show-mcdc %S/Inputs/mcdc-general.o32l -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp -format html -o %t.html.dir +// RUN: FileCheck -check-prefix=HTML -input-file=%t.html.dir/coverage/tmp/mcdc-general.cpp.html %s +// HTML-COUNT-4: MC/DC Decision Region ( + +// RUN: FileCheck -check-prefix HTML-INDEX -input-file %t.html.dir/index.html %s +// HTML-INDEX-LABEL: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX:
FilenameFunction CoverageLine CoverageRegion CoverageBranch CoverageMC/DC +// HTML-INDEX: 100.00% (2/2) +// HTML-INDEX: 100.00% (20/20) +// HTML-INDEX: 100.00% (26/26) +// HTML-INDEX: 91.67% (22/24) +// HTML-INDEX: 83.33% (10/12) +// HTML-INDEX: Totals Index: llvm/test/tools/llvm-profdata/Inputs/mcdc-1.proftext =================================================================== --- /dev/null +++ llvm/test/tools/llvm-profdata/Inputs/mcdc-1.proftext @@ -0,0 +1,16 @@ +main +# Func Hash: +702755447896 +# Num Counters: +4 +# Counter Values: +1 +0 +1 +0 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +2 + + Index: llvm/test/tools/llvm-profdata/Inputs/mcdc-2.proftext =================================================================== --- /dev/null +++ llvm/test/tools/llvm-profdata/Inputs/mcdc-2.proftext @@ -0,0 +1,35 @@ +main +# Func Hash: +702755447896 +# Num Counters: +4 +# Counter Values: +1 +1 +1 +1 +# Num Bitmask Bytes: +$1 +# Bitmask Byte Values: +8 + + +test3 +# Func Hash: +15288018065 +# Num Counters: +6 +# Counter Values: +4 +2 +1 +0 +0 +2 +# Num Bitmask Bytes: +$2 +# Bitmask Byte Values: +29 +0 + + Index: llvm/test/tools/llvm-profdata/Inputs/mcdc-3.proftext =================================================================== --- /dev/null +++ llvm/test/tools/llvm-profdata/Inputs/mcdc-3.proftext @@ -0,0 +1,23 @@ +test3 +# Func Hash: +15288018065 +# Num Counters: +6 +# Counter Values: +4 +2 +1 +0 +0 +2 +# Num Bitmask Bytes: +$8 +# Bitmask Byte Values: +0 +2 +3 +f +f +a +c +2 Index: llvm/test/tools/llvm-profdata/Inputs/mcdc-4.proftext =================================================================== --- /dev/null +++ llvm/test/tools/llvm-profdata/Inputs/mcdc-4.proftext @@ -0,0 +1,23 @@ +test3 +# Func Hash: +15288018065 +# Num Counters: +6 +# Counter Values: +4 +2 +1 +0 +0 +2 +# Num Bitmask Bytes: +$8 +# Bitmask Byte Values: +1 +2 +3 +4 +5 +6 +7 +8 Index: llvm/test/tools/llvm-profdata/Inputs/mcdc-err0.proftext =================================================================== --- /dev/null +++ llvm/test/tools/llvm-profdata/Inputs/mcdc-err0.proftext @@ -0,0 +1,23 @@ +test3 +# Func Hash: +15288018065 +# Num Counters: +6 +# Counter Values: +4 +2 +1 +0 +0 +2 +# Num Bitmask Bytes: +$8.9 +# Bitmask Byte Values: +1 +2 +3 +4 +5 +6 +7 +8 Index: llvm/test/tools/llvm-profdata/Inputs/mcdc-err1.proftext =================================================================== --- /dev/null +++ llvm/test/tools/llvm-profdata/Inputs/mcdc-err1.proftext @@ -0,0 +1,23 @@ +test3 +# Func Hash: +15288018065 +# Num Counters: +6 +# Counter Values: +4 +2 +1 +0 +0 +2 +# Num Bitmask Bytes: +$8 +# Bitmask Byte Values: +1 +2 +3 +4 +5.4 +6 +7 +8 Index: llvm/test/tools/llvm-profdata/binary-ids-padding.test =================================================================== --- llvm/test/tools/llvm-profdata/binary-ids-padding.test +++ llvm/test/tools/llvm-profdata/binary-ids-padding.test @@ -5,8 +5,10 @@ // INSTR_PROF_RAW_HEADER(uint64_t, BinaryIdsSize, __llvm_write_binary_ids(NULL)) // INSTR_PROF_RAW_HEADER(uint64_t, DataSize, DataSize) // INSTR_PROF_RAW_HEADER(uint64_t, CountersSize, CountersSize) +// INSTR_PROF_RAW_HEADER(uint64_t, NumBitmaskBytes, NumBitmaskBytes) // INSTR_PROF_RAW_HEADER(uint64_t, NamesSize, NamesSize) // INSTR_PROF_RAW_HEADER(uint64_t, CountersDelta, (uintptr_t)CountersBegin) +// INSTR_PROF_RAW_HEADER(uint64_t, BitmaskDelta, (uintptr_t)BitmaskBegin) // INSTR_PROF_RAW_HEADER(uint64_t, NamesDelta, (uintptr_t)NamesBegin) // INSTR_PROF_RAW_HEADER(uint64_t, ValueKindLast, IPVK_Last) @@ -23,8 +25,11 @@ RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\3\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\20\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\4\0\1\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\4\0\2\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw @@ -51,14 +56,18 @@ RUN: printf '\0\0\4\0\1\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\1\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\067\265\035\031\112\165\023\344' >> %t.profraw RUN: printf '\02\0\0\0\0\0\0\0' >> %t.profraw -RUN: printf '\xd8\xff\3\0\1\0\0\0' >> %t.profraw +RUN: printf '\xc8\xff\3\0\1\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\02\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\023\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\067\0\0\0\0\0\0\0' >> %t.profraw Index: llvm/test/tools/llvm-profdata/large-binary-id-size.test =================================================================== --- llvm/test/tools/llvm-profdata/large-binary-id-size.test +++ llvm/test/tools/llvm-profdata/large-binary-id-size.test @@ -9,6 +9,9 @@ RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw // Check for a corrupted size being too large past the end of the file. RUN: printf '\7\7\7\7\7\7\7\7' >> %t.profraw Index: llvm/test/tools/llvm-profdata/malformed-not-space-for-another-header.test =================================================================== --- llvm/test/tools/llvm-profdata/malformed-not-space-for-another-header.test +++ llvm/test/tools/llvm-profdata/malformed-not-space-for-another-header.test @@ -5,8 +5,10 @@ // INSTR_PROF_RAW_HEADER(uint64_t, BinaryIdsSize, __llvm_write_binary_ids(NULL)) // INSTR_PROF_RAW_HEADER(uint64_t, DataSize, DataSize) // INSTR_PROF_RAW_HEADER(uint64_t, CountersSize, CountersSize) +// INSTR_PROF_RAW_HEADER(uint64_t, NumBitmaskBytes, NumBitmaskBytes) // INSTR_PROF_RAW_HEADER(uint64_t, NamesSize, NamesSize) // INSTR_PROF_RAW_HEADER(uint64_t, CountersDelta, (uintptr_t)CountersBegin) +// INSTR_PROF_RAW_HEADER(uint64_t, BitmaskDelta, (uintptr_t)BitmaskBegin) // INSTR_PROF_RAW_HEADER(uint64_t, NamesDelta, (uintptr_t)NamesBegin) // INSTR_PROF_RAW_HEADER(uint64_t, ValueKindLast, IPVK_Last) @@ -17,8 +19,11 @@ RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\1\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\10\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\4\0\1\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\4\0\2\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw @@ -35,7 +40,9 @@ RUN: printf '\0\0\4\0\1\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\1\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\023\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\3\0foo\0\0\0' >> %t.profraw Index: llvm/test/tools/llvm-profdata/malformed-num-counters-zero.test =================================================================== --- llvm/test/tools/llvm-profdata/malformed-num-counters-zero.test +++ llvm/test/tools/llvm-profdata/malformed-num-counters-zero.test @@ -5,8 +5,10 @@ // INSTR_PROF_RAW_HEADER(uint64_t, BinaryIdsSize, __llvm_write_binary_ids(NULL)) // INSTR_PROF_RAW_HEADER(uint64_t, DataSize, DataSize) // INSTR_PROF_RAW_HEADER(uint64_t, CountersSize, CountersSize) +// INSTR_PROF_RAW_HEADER(uint64_t, NumBitmaskBytes, NumBitmaskBytes) // INSTR_PROF_RAW_HEADER(uint64_t, NamesSize, NamesSize) // INSTR_PROF_RAW_HEADER(uint64_t, CountersDelta, (uintptr_t)CountersBegin) +// INSTR_PROF_RAW_HEADER(uint64_t, BitmaskDelta, (uintptr_t)BitmaskBegin) // INSTR_PROF_RAW_HEADER(uint64_t, NamesDelta, (uintptr_t)NamesBegin) // INSTR_PROF_RAW_HEADER(uint64_t, ValueKindLast, IPVK_Last) @@ -17,8 +19,12 @@ RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\1\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\10\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\4\0\1\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\4\0\2\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw @@ -35,8 +41,10 @@ RUN: printf '\0\0\4\0\1\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw // Make NumCounters = 0 so that we get "number of counters is zero" error message RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\023\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\3\0foo\0\0\0' >> %t.profraw Index: llvm/test/tools/llvm-profdata/malformed-ptr-to-counter-array.test =================================================================== --- llvm/test/tools/llvm-profdata/malformed-ptr-to-counter-array.test +++ llvm/test/tools/llvm-profdata/malformed-ptr-to-counter-array.test @@ -5,8 +5,10 @@ // INSTR_PROF_RAW_HEADER(uint64_t, BinaryIdsSize, __llvm_write_binary_ids(NULL)) // INSTR_PROF_RAW_HEADER(uint64_t, DataSize, DataSize) // INSTR_PROF_RAW_HEADER(uint64_t, CountersSize, CountersSize) +// INSTR_PROF_RAW_HEADER(uint64_t, NumBitmaskBytes, NumBitmaskBytes) // INSTR_PROF_RAW_HEADER(uint64_t, NamesSize, NamesSize) // INSTR_PROF_RAW_HEADER(uint64_t, CountersDelta, (uintptr_t)CountersBegin) +// INSTR_PROF_RAW_HEADER(uint64_t, BitmaskDelta, (uintptr_t)BitmaskBegin) // INSTR_PROF_RAW_HEADER(uint64_t, NamesDelta, (uintptr_t)NamesBegin) // INSTR_PROF_RAW_HEADER(uint64_t, ValueKindLast, IPVK_Last) @@ -17,8 +19,11 @@ RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\2\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\10\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\6\0\1\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\6\0\2\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw @@ -38,10 +43,12 @@ // Octal '\11' is 9 in decimal: this should push CounterOffset to 1. As there are two counters, // the profile reader should error out. RUN: printf '\11\0\6\0\1\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw RUN: printf '\02\0\0\0\0\0\0\0' >> %t.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t.profraw // Counter Section Index: llvm/test/tools/llvm-profdata/mcdc-bitmask.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-profdata/mcdc-bitmask.test @@ -0,0 +1,54 @@ +# Test MC/DC bitmask reading and merging. + +# Merge as profdata. +RUN: llvm-profdata merge %p/Inputs/mcdc-1.proftext %p/Inputs/mcdc-2.proftext -o %t.profdata +RUN: llvm-profdata show %t.profdata --text -all-functions | FileCheck %s --check-prefix=MCDC +# Merge as proftext. +RUN: llvm-profdata merge %p/Inputs/mcdc-1.proftext %p/Inputs/mcdc-2.proftext -o %t.proftext +RUN: llvm-profdata show %t.proftext --text -all-functions | FileCheck %s --check-prefix=MCDC + +MCDC: # Num Bitmask Bytes: +MCDC-NEXT: $1 +MCDC-NEXT: # Bitmask Byte Values: +MCDC-NEXT: a +MCDC: # Num Bitmask Bytes: +MCDC-NEXT: $2 +MCDC-NEXT: # Bitmask Byte Values: +MCDC-NEXT: 29 +MCDC-NEXT: 0 + +# Merge as profdata. +RUN: llvm-profdata merge %p/Inputs/mcdc-3.proftext %p/Inputs/mcdc-4.proftext -o %t.profdata +RUN: llvm-profdata show %t.profdata --text -all-functions | FileCheck %s --check-prefix=MCDC2 +# Merge as proftext. +RUN: llvm-profdata merge %p/Inputs/mcdc-3.proftext %p/Inputs/mcdc-4.proftext -o %t.proftext +RUN: llvm-profdata show %t.proftext --text -all-functions | FileCheck %s --check-prefix=MCDC2 + +MCDC2: # Num Bitmask Bytes: +MCDC2-NEXT: $8 +MCDC2-NEXT: # Bitmask Byte Values: +MCDC2-NEXT: 1 +MCDC2-NEXT: 2 +MCDC2-NEXT: 3 +MCDC2-NEXT: f +MCDC2-NEXT: f +MCDC2-NEXT: e +MCDC2-NEXT: f +MCDC2-NEXT: a + +# Incompatible size mismatch. +RUN: llvm-profdata merge %p/Inputs/mcdc-2.proftext %p/Inputs/mcdc-4.proftext -o %t.profdata 2>&1 | FileCheck %s --check-prefix=MCDC3 +# Merge as proftext +RUN: llvm-profdata merge %p/Inputs/mcdc-2.proftext %p/Inputs/mcdc-4.proftext -o %t.proftext 2>&1 | FileCheck %s --check-prefix=MCDC3 + +MCDC3: function bitmask size change detected (bitmask size mismatch) + +# Invalid number of bitmask bytes. +RUN: not llvm-profdata merge %p/Inputs/mcdc-3.proftext %p/Inputs/mcdc-err0.proftext -o %t.proftext 2>&1 | FileCheck %s --check-prefix=MCDC4 + +MCDC4: malformed instrumentation profile data: number of bitmasks is not a valid integer + +# Invalid bitmask byte. +RUN: not llvm-profdata merge %p/Inputs/mcdc-3.proftext %p/Inputs/mcdc-err1.proftext -o %t.proftext 2>&1 | FileCheck %s --check-prefix=MCDC5 + +MCDC5: malformed instrumentation profile data: bitmask byte is invalid Index: llvm/test/tools/llvm-profdata/raw-32-bits-be.test =================================================================== --- llvm/test/tools/llvm-profdata/raw-32-bits-be.test +++ llvm/test/tools/llvm-profdata/raw-32-bits-be.test @@ -5,33 +5,42 @@ RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\3' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t +RUN: printf '\0\0\0\0\0\0\0\4' >> %t +RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\20' >> %t RUN: printf '\0\0\0\0\1\0\0\0' >> %t +RUN: printf '\0\0\0\0\3\0\0\0' >> %t RUN: printf '\0\0\0\0\2\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\134\370\302\114\333\030\275\254' >> %t RUN: printf '\0\0\0\0\0\0\0\1' >> %t RUN: printf '\1\0\0\0' >> %t +RUN: printf '\3\0\0\0' >> %t RUN: printf '\0\0\0\0' >> %t RUN: printf '\0\0\0\0' >> %t RUN: printf '\0\0\0\1' >> %t -RUN: printf '\0\0\0\0\0\0\0\0' >> %t +RUN: printf '\0\0\0\0\0\0\0\3' >> %t +RUN: printf '\0\0\0\0' >> %t RUN: printf '\344\023\165\112\031\035\265\067' >> %t RUN: printf '\0\0\0\0\0\0\0\2' >> %t -RUN: printf '\0\xff\xff\xe0' >> %t +RUN: printf '\0\xff\xff\xd8' >> %t +RUN: printf '\2\xff\xff\xd3' >> %t RUN: printf '\0\0\0\0' >> %t RUN: printf '\0\0\0\0' >> %t RUN: printf '\0\0\0\2' >> %t -RUN: printf '\0\0\0\0\0\0\0\0' >> %t +RUN: printf '\0\0\0\0\0\0\0\1' >> %t +RUN: printf '\0\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\023' >> %t RUN: printf '\0\0\0\0\0\0\0\067' >> %t RUN: printf '\0\0\0\0\0\0\0\101' >> %t +RUN: printf '\125\125\125\052' >> %t RUN: printf '\7\0foo\1bar\0\0\0\0\0\0\0' >> %t RUN: llvm-profdata show %t -all-functions -counts | FileCheck %s +RUN: llvm-profdata show %t -all-functions -text | FileCheck %s -check-prefix=MCDC CHECK: Counters: CHECK: foo: @@ -48,3 +57,14 @@ CHECK: Total functions: 2 CHECK: Maximum function count: 55 CHECK: Maximum internal block count: 65 + +MCDC: Num Bitmask Bytes: +MCDC-NEXT: $3 +MCDC-NEXT: Bitmask Byte Values: +MCDC-NEXT: 55 +MCDC-NEXT: 55 +MCDC-NEXT: 55 +MCDC: Num Bitmask Bytes: +MCDC-NEXT: $1 +MCDC-NEXT: Bitmask Byte Values: +MCDC-NEXT: 2a Index: llvm/test/tools/llvm-profdata/raw-32-bits-le.test =================================================================== --- llvm/test/tools/llvm-profdata/raw-32-bits-le.test +++ llvm/test/tools/llvm-profdata/raw-32-bits-le.test @@ -5,33 +5,42 @@ RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\3\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t +RUN: printf '\4\0\0\0\0\0\0\0' >> %t +RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\20\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\1\0\0\0\0' >> %t +RUN: printf '\0\0\0\3\0\0\0\0' >> %t RUN: printf '\0\0\0\2\0\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\254\275\030\333\114\302\370\134' >> %t RUN: printf '\1\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\1' >> %t +RUN: printf '\0\0\0\3' >> %t RUN: printf '\0\0\0\0' >> %t RUN: printf '\0\0\0\0' >> %t RUN: printf '\1\0\0\0' >> %t -RUN: printf '\0\0\0\0\0\0\0\0' >> %t +RUN: printf '\0\0\0\0\3\0\0\0' >> %t +RUN: printf '\0\0\0\0' >> %t RUN: printf '\067\265\035\031\112\165\023\344' >> %t RUN: printf '\02\0\0\0\0\0\0\0' >> %t -RUN: printf '\xe0\xff\xff\0' >> %t +RUN: printf '\xd8\xff\xff\0' >> %t +RUN: printf '\xd3\xff\xff\2' >> %t RUN: printf '\0\0\0\0' >> %t RUN: printf '\0\0\0\0' >> %t RUN: printf '\2\0\0\0' >> %t -RUN: printf '\0\0\0\0\0\0\0\0' >> %t +RUN: printf '\0\0\0\0\1\0\0\0' >> %t +RUN: printf '\0\0\0\0' >> %t RUN: printf '\023\0\0\0\0\0\0\0' >> %t RUN: printf '\067\0\0\0\0\0\0\0' >> %t RUN: printf '\101\0\0\0\0\0\0\0' >> %t +RUN: printf '\125\125\125\052' >> %t RUN: printf '\7\0foo\1bar\0\0\0\0\0\0\0' >> %t RUN: llvm-profdata show %t -all-functions -counts | FileCheck %s +RUN: llvm-profdata show %t -all-functions -text | FileCheck %s -check-prefix=MCDC CHECK: Counters: CHECK: foo: @@ -48,3 +57,14 @@ CHECK: Total functions: 2 CHECK: Maximum function count: 55 CHECK: Maximum internal block count: 65 + +MCDC: Num Bitmask Bytes: +MCDC-NEXT: $3 +MCDC-NEXT: Bitmask Byte Values: +MCDC-NEXT: 55 +MCDC-NEXT: 55 +MCDC-NEXT: 55 +MCDC: Num Bitmask Bytes: +MCDC-NEXT: $1 +MCDC-NEXT: Bitmask Byte Values: +MCDC-NEXT: 2a Index: llvm/test/tools/llvm-profdata/raw-64-bits-be.test =================================================================== --- llvm/test/tools/llvm-profdata/raw-64-bits-be.test +++ llvm/test/tools/llvm-profdata/raw-64-bits-be.test @@ -5,31 +5,40 @@ RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\3' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t +RUN: printf '\0\0\0\0\0\0\0\4' >> %t +RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\20' >> %t RUN: printf '\0\0\0\1\0\4\0\0' >> %t +RUN: printf '\0\0\0\3\0\4\0\0' >> %t RUN: printf '\0\0\0\2\0\4\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\134\370\302\114\333\030\275\254' >> %t RUN: printf '\0\0\0\0\0\0\0\1' >> %t RUN: printf '\0\0\0\1\0\4\0\0' >> %t +RUN: printf '\0\0\0\3\0\4\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\1\0\0\0\0' >> %t +RUN: printf '\0\0\0\3\0\0\0\0' >> %t RUN: printf '\344\023\165\112\031\035\265\067' >> %t RUN: printf '\0\0\0\0\0\0\0\02' >> %t -RUN: printf '\0\0\0\1\0\3\xff\xd8' >> %t +RUN: printf '\0\0\0\1\0\3\xff\xc8' >> %t +RUN: printf '\0\0\0\3\0\3\xff\xc3' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\02\0\0\0\0' >> %t +RUN: printf '\0\0\0\1\0\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\023' >> %t RUN: printf '\0\0\0\0\0\0\0\067' >> %t RUN: printf '\0\0\0\0\0\0\0\101' >> %t +RUN: printf '\125\125\125\052' >> %t RUN: printf '\7\0foo\1bar\0\0\0\0\0\0\0' >> %t RUN: llvm-profdata show %t -all-functions -counts | FileCheck %s +RUN: llvm-profdata show %t -all-functions -text | FileCheck %s -check-prefix=MCDC CHECK: Counters: CHECK: foo: @@ -46,3 +55,14 @@ CHECK: Total functions: 2 CHECK: Maximum function count: 55 CHECK: Maximum internal block count: 65 + +MCDC: Num Bitmask Bytes: +MCDC-NEXT: $3 +MCDC-NEXT: Bitmask Byte Values: +MCDC-NEXT: 55 +MCDC-NEXT: 55 +MCDC-NEXT: 55 +MCDC: Num Bitmask Bytes: +MCDC-NEXT: $1 +MCDC-NEXT: Bitmask Byte Values: +MCDC-NEXT: 2a Index: llvm/test/tools/llvm-profdata/raw-64-bits-le.test =================================================================== --- llvm/test/tools/llvm-profdata/raw-64-bits-le.test +++ llvm/test/tools/llvm-profdata/raw-64-bits-le.test @@ -5,31 +5,40 @@ RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\3\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t +RUN: printf '\4\0\0\0\0\0\0\0' >> %t +RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\20\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\4\0\1\0\0\0' >> %t +RUN: printf '\0\0\4\0\3\0\0\0' >> %t RUN: printf '\0\0\4\0\2\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\254\275\030\333\114\302\370\134' >> %t RUN: printf '\1\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\4\0\1\0\0\0' >> %t +RUN: printf '\0\0\4\0\3\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\1\0\0\0\0\0\0\0' >> %t +RUN: printf '\3\0\0\0\0\0\0\0' >> %t RUN: printf '\067\265\035\031\112\165\023\344' >> %t RUN: printf '\02\0\0\0\0\0\0\0' >> %t -RUN: printf '\xd8\xff\3\0\1\0\0\0' >> %t +RUN: printf '\xc8\xff\3\0\1\0\0\0' >> %t +RUN: printf '\xc3\xff\3\0\3\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\0\0\0\0\0\0\0\0' >> %t RUN: printf '\02\0\0\0\0\0\0\0' >> %t +RUN: printf '\1\0\0\0\0\0\0\0' >> %t RUN: printf '\023\0\0\0\0\0\0\0' >> %t RUN: printf '\067\0\0\0\0\0\0\0' >> %t RUN: printf '\101\0\0\0\0\0\0\0' >> %t +RUN: printf '\125\125\125\052' >> %t RUN: printf '\7\0foo\1bar\0\0\0\0\0\0\0' >> %t RUN: llvm-profdata show %t -all-functions -counts | FileCheck %s +RUN: llvm-profdata show %t -all-functions -text | FileCheck %s -check-prefix=MCDC CHECK: Counters: CHECK: foo: @@ -46,3 +55,14 @@ CHECK: Total functions: 2 CHECK: Maximum function count: 55 CHECK: Maximum internal block count: 65 + +MCDC: Num Bitmask Bytes: +MCDC-NEXT: $3 +MCDC-NEXT: Bitmask Byte Values: +MCDC-NEXT: 55 +MCDC-NEXT: 55 +MCDC-NEXT: 55 +MCDC: Num Bitmask Bytes: +MCDC-NEXT: $1 +MCDC-NEXT: Bitmask Byte Values: +MCDC-NEXT: 2a Index: llvm/test/tools/llvm-profdata/raw-two-profiles.test =================================================================== --- llvm/test/tools/llvm-profdata/raw-two-profiles.test +++ llvm/test/tools/llvm-profdata/raw-two-profiles.test @@ -5,8 +5,11 @@ RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw RUN: printf '\1\0\0\0\0\0\0\0' >> %t-foo.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw RUN: printf '\10\0\0\0\0\0\0\0' >> %t-foo.profraw RUN: printf '\0\0\4\0\1\0\0\0' >> %t-foo.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw RUN: printf '\0\0\4\0\2\0\0\0' >> %t-foo.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw @@ -15,7 +18,9 @@ RUN: printf '\0\0\4\0\1\0\0\0' >> %t-foo.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw RUN: printf '\1\0\0\0\0\0\0\0' >> %t-foo.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw RUN: printf '\023\0\0\0\0\0\0\0' >> %t-foo.profraw RUN: printf '\3\0foo\0\0\0' >> %t-foo.profraw @@ -27,8 +32,11 @@ RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw RUN: printf '\2\0\0\0\0\0\0\0' >> %t-bar.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw RUN: printf '\10\0\0\0\0\0\0\0' >> %t-bar.profraw RUN: printf '\0\0\6\0\1\0\0\0' >> %t-bar.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw RUN: printf '\0\0\6\0\2\0\0\0' >> %t-bar.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw @@ -37,7 +45,9 @@ RUN: printf '\0\0\6\0\1\0\0\0' >> %t-bar.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw RUN: printf '\02\0\0\0\0\0\0\0' >> %t-bar.profraw +RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw RUN: printf '\067\0\0\0\0\0\0\0' >> %t-bar.profraw RUN: printf '\101\0\0\0\0\0\0\0' >> %t-bar.profraw Index: llvm/tools/llvm-cov/CodeCoverage.cpp =================================================================== --- llvm/tools/llvm-cov/CodeCoverage.cpp +++ llvm/tools/llvm-cov/CodeCoverage.cpp @@ -100,6 +100,11 @@ const MemoryBuffer &File, CoverageData &CoverageInfo); + /// Create source views for the MCDC records. + void attachMCDCSubViews(SourceCoverageView &View, StringRef SourceName, + ArrayRef MCDCRecords, + const MemoryBuffer &File, CoverageData &CoverageInfo); + /// Create the source view of a particular function. std::unique_ptr createFunctionView(const FunctionRecord &Function, @@ -342,6 +347,36 @@ } } +void CodeCoverageTool::attachMCDCSubViews(SourceCoverageView &View, + StringRef SourceName, + ArrayRef MCDCRecords, + const MemoryBuffer &File, + CoverageData &CoverageInfo) { + if (!ViewOpts.ShowMCDC) + return; + + const auto *NextRecord = MCDCRecords.begin(); + const auto *EndRecord = MCDCRecords.end(); + + // Group and process MCDC records that have the same line number into the + // same subview. + while (NextRecord != EndRecord) { + std::vector ViewMCDCRecords; + unsigned CurrentLine = NextRecord->getDecisionRegion().LineEnd; + + while (NextRecord != EndRecord && + CurrentLine == NextRecord->getDecisionRegion().LineEnd) { + ViewMCDCRecords.push_back(*NextRecord++); + } + + if (!ViewMCDCRecords.empty()) { + auto SubView = SourceCoverageView::create(SourceName, File, ViewOpts, + std::move(CoverageInfo)); + View.addMCDCRecord(CurrentLine, ViewMCDCRecords, std::move(SubView)); + } + } +} + std::unique_ptr CodeCoverageTool::createFunctionView(const FunctionRecord &Function, const CoverageMapping &Coverage) { @@ -354,12 +389,15 @@ auto Branches = FunctionCoverage.getBranches(); auto Expansions = FunctionCoverage.getExpansions(); + auto MCDCRecords = FunctionCoverage.getMCDCRecords(); auto View = SourceCoverageView::create(DC.demangle(Function.Name), SourceBuffer.get(), ViewOpts, std::move(FunctionCoverage)); attachExpansionSubViews(*View, Expansions, Coverage); attachBranchSubViews(*View, DC.demangle(Function.Name), Branches, SourceBuffer.get(), FunctionCoverage); + attachMCDCSubViews(*View, DC.demangle(Function.Name), MCDCRecords, + SourceBuffer.get(), FunctionCoverage); return View; } @@ -376,11 +414,14 @@ auto Branches = FileCoverage.getBranches(); auto Expansions = FileCoverage.getExpansions(); + auto MCDCRecords = FileCoverage.getMCDCRecords(); auto View = SourceCoverageView::create(SourceFile, SourceBuffer.get(), ViewOpts, std::move(FileCoverage)); attachExpansionSubViews(*View, Expansions, Coverage); attachBranchSubViews(*View, SourceFile, Branches, SourceBuffer.get(), FileCoverage); + attachMCDCSubViews(*View, SourceFile, MCDCRecords, SourceBuffer.get(), + FileCoverage); if (!ViewOpts.ShowFunctionInstantiations) return View; @@ -398,11 +439,14 @@ auto SubViewCoverage = Coverage.getCoverageForFunction(*Function); auto SubViewExpansions = SubViewCoverage.getExpansions(); auto SubViewBranches = SubViewCoverage.getBranches(); + auto SubViewMCDCRecords = SubViewCoverage.getMCDCRecords(); SubView = SourceCoverageView::create( Funcname, SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage)); attachExpansionSubViews(*SubView, SubViewExpansions, Coverage); attachBranchSubViews(*SubView, SourceFile, SubViewBranches, SourceBuffer.get(), SubViewCoverage); + attachMCDCSubViews(*SubView, SourceFile, SubViewMCDCRecords, + SourceBuffer.get(), SubViewCoverage); } unsigned FileID = Function->CountedRegions.front().FileID; @@ -724,6 +768,11 @@ cl::desc("Show branch condition statistics in summary table"), cl::init(true)); + cl::opt MCDCSummary( + "show-mcdc-summary", cl::Optional, + cl::desc("Show MCDC statistics in summary table"), + cl::init(false)); + cl::opt InstantiationSummary( "show-instantiation-summary", cl::Optional, cl::desc("Show instantiation statistics in summary table")); @@ -882,6 +931,7 @@ ::exit(0); } + ViewOpts.ShowMCDCSummary = MCDCSummary; ViewOpts.ShowBranchSummary = BranchSummary; ViewOpts.ShowRegionSummary = RegionSummary; ViewOpts.ShowInstantiationSummary = InstantiationSummary; @@ -927,6 +977,11 @@ "percent", "Show True/False percent")), cl::init(CoverageViewOptions::BranchOutputType::Off)); + cl::opt ShowMCDC( + "show-mcdc", cl::Optional, + cl::desc("Show the MCDC Coverage for each applicable boolean expression"), + cl::cat(ViewCategory)); + cl::opt ShowBestLineRegionsCounts( "show-line-counts-or-regions", cl::Optional, cl::desc("Show the execution counts for each line, or the execution " @@ -1018,6 +1073,7 @@ ViewOpts.ShowExpandedRegions = ShowExpansions; ViewOpts.ShowBranchCounts = ShowBranches == CoverageViewOptions::BranchOutputType::Count; + ViewOpts.ShowMCDC = ShowMCDC; ViewOpts.ShowBranchPercents = ShowBranches == CoverageViewOptions::BranchOutputType::Percent; ViewOpts.ShowFunctionInstantiations = ShowInstantiations; Index: llvm/tools/llvm-cov/CoverageExporterJson.cpp =================================================================== --- llvm/tools/llvm-cov/CoverageExporterJson.cpp +++ llvm/tools/llvm-cov/CoverageExporterJson.cpp @@ -20,6 +20,8 @@ // -- File: dict => Coverage for a single file // -- Branches: array => List of Branches in the file // -- Branch: dict => Describes a branch of the file with counters +// -- MCDC Records: array => List of MCDC records in the file +// -- MCDC Values: array => List of T/F covered condition values // -- Segments: array => List of Segments contained in the file // -- Segment: dict => Describes a segment of the file with a counter // -- Expansions: array => List of expansion records @@ -34,6 +36,7 @@ // -- FunctionCoverage: dict => Object summarizing function coverage // -- RegionCoverage: dict => Object summarizing region coverage // -- BranchCoverage: dict => Object summarizing branch coverage +// -- MCDCCoverage: dict => Object summarizing MC/DC coverage // -- Functions: array => List of objects describing coverage for functions // -- Function: dict => Coverage info for a single function // -- Filenames: array => List of filenames that the function relates to @@ -43,6 +46,7 @@ // -- InstantiationCoverage: dict => Object summarizing inst. coverage // -- RegionCoverage: dict => Object summarizing region coverage // -- BranchCoverage: dict => Object summarizing branch coverage +// -- MCDCCoverage: dict => Object summarizing MC/DC coverage // //===----------------------------------------------------------------------===// @@ -98,6 +102,20 @@ Region.ExpandedFileID, int64_t(Region.Kind)}); } +json::Array gatherConditions(const coverage::MCDCRecord &Record) { + json::Array Conditions; + for (unsigned c = 0; c < Record.getNumConditions(); c++) + Conditions.push_back(Record.isCondIndepPairCovered(c)); + return Conditions; +} + +json::Array renderMCDCRecord(const coverage::MCDCRecord &Record) { + const llvm::coverage::CounterMappingRegion &CMR = Record.getDecisionRegion(); + return json::Array({CMR.LineStart, CMR.ColumnStart, CMR.LineEnd, + CMR.ColumnEnd, CMR.ExpandedFileID, int64_t(CMR.Kind), + gatherConditions(Record)}); +} + json::Array renderRegions(ArrayRef Regions) { json::Array RegionArray; for (const auto &Region : Regions) @@ -113,6 +131,13 @@ return RegionArray; } +json::Array renderMCDCRecords(ArrayRef Records) { + json::Array RecordArray; + for (auto &Record : Records) + RecordArray.push_back(renderMCDCRecord(Record)); + return RecordArray; +} + std::vector collectNestedBranches(const coverage::CoverageMapping &Coverage, ArrayRef Expansions) { @@ -179,7 +204,14 @@ {"covered", int64_t(Summary.BranchCoverage.getCovered())}, {"notcovered", int64_t(Summary.BranchCoverage.getNumBranches() - Summary.BranchCoverage.getCovered())}, - {"percent", Summary.BranchCoverage.getPercentCovered()}})}}); + {"percent", Summary.BranchCoverage.getPercentCovered()}})}, + {"mcdc", + json::Object( + {{"count", int64_t(Summary.MCDCCoverage.getNumPairs())}, + {"covered", int64_t(Summary.MCDCCoverage.getCoveredPairs())}, + {"notcovered", int64_t(Summary.MCDCCoverage.getNumPairs() - + Summary.MCDCCoverage.getCoveredPairs())}, + {"percent", Summary.MCDCCoverage.getPercentCovered()}})}}); } json::Array renderFileExpansions(const coverage::CoverageMapping &Coverage, @@ -207,6 +239,14 @@ return BranchArray; } +json::Array renderFileMCDC(const coverage::CoverageData &FileCoverage, + const FileCoverageSummary &FileReport) { + json::Array MCDCRecordArray; + for (const auto &Record : FileCoverage.getMCDCRecords()) + MCDCRecordArray.push_back(renderMCDCRecord(Record)); + return MCDCRecordArray; +} + json::Object renderFile(const coverage::CoverageMapping &Coverage, const std::string &Filename, const FileCoverageSummary &FileReport, @@ -217,6 +257,7 @@ auto FileCoverage = Coverage.getCoverageForFile(Filename); File["segments"] = renderFileSegments(FileCoverage, FileReport); File["branches"] = renderFileBranches(FileCoverage, FileReport); + File["mcdc_records"] = renderFileMCDC(FileCoverage, FileReport); if (!Options.SkipExpansions) { File["expansions"] = renderFileExpansions(Coverage, FileCoverage, FileReport); @@ -265,6 +306,7 @@ {"count", clamp_uint64_to_int64(F.ExecutionCount)}, {"regions", renderRegions(F.CountedRegions)}, {"branches", renderBranchRegions(F.CountedBranchRegions)}, + {"mcdc_records", renderMCDCRecords(F.MCDCRecords)}, {"filenames", json::Array(F.Filenames)}})); return FunctionArray; } Index: llvm/tools/llvm-cov/CoverageReport.cpp =================================================================== --- llvm/tools/llvm-cov/CoverageReport.cpp +++ llvm/tools/llvm-cov/CoverageReport.cpp @@ -86,9 +86,9 @@ } // Specify the default column widths. -size_t FileReportColumns[] = {25, 12, 18, 10, 12, 18, 10, 16, - 16, 10, 12, 18, 10, 12, 18, 10}; -size_t FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8, 10, 8, 8}; +size_t FileReportColumns[] = {25, 12, 18, 10, 12, 18, 10, 16, 16, 10, + 12, 18, 10, 12, 18, 10, 20, 21, 10}; +size_t FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8, 10, 8, 8, 20, 8, 8}; /// Adjust column widths to fit long file paths and function names. void adjustColumnWidths(ArrayRef Files, @@ -256,6 +256,22 @@ OS << column("-", FileReportColumns[15], Column::RightAlignment); } + if (Options.ShowMCDCSummary) { + OS << format("%*u", FileReportColumns[16], + (unsigned)File.MCDCCoverage.getNumPairs()); + Options.colored_ostream(OS, LineCoverageColor) + << format("%*u", FileReportColumns[17], + (unsigned)(File.MCDCCoverage.getNumPairs() - + File.MCDCCoverage.getCoveredPairs())); + if (File.MCDCCoverage.getNumPairs()) + Options.colored_ostream(OS, LineCoverageColor) + << format("%*.2f", FileReportColumns[18] - 1, + File.MCDCCoverage.getPercentCovered()) + << '%'; + else + OS << column("-", FileReportColumns[18], Column::RightAlignment); + } + OS << "\n"; } @@ -303,6 +319,19 @@ Function.BranchCoverage.getPercentCovered()) << '%'; } + if (Options.ShowMCDCSummary) { + OS << format("%*u", FunctionReportColumns[10], + (unsigned)Function.MCDCCoverage.getNumPairs()); + Options.colored_ostream(OS, LineCoverageColor) + << format("%*u", FunctionReportColumns[11], + (unsigned)(Function.MCDCCoverage.getNumPairs() - + Function.MCDCCoverage.getCoveredPairs())); + Options.colored_ostream( + OS, determineCoveragePercentageColor(Function.MCDCCoverage)) + << format("%*.2f", FunctionReportColumns[12] - 1, + Function.MCDCCoverage.getPercentCovered()) + << '%'; + } OS << "\n"; } @@ -335,6 +364,11 @@ OS << column("Branches", FunctionReportColumns[7], Column::RightAlignment) << column("Miss", FunctionReportColumns[8], Column::RightAlignment) << column("Cover", FunctionReportColumns[9], Column::RightAlignment); + if (Options.ShowMCDCSummary) + OS << column("MC/DC Conditions", FunctionReportColumns[10], + Column::RightAlignment) + << column("Miss", FunctionReportColumns[11], Column::RightAlignment) + << column("Cover", FunctionReportColumns[12], Column::RightAlignment); OS << "\n"; renderDivider(FunctionReportColumns, OS); OS << "\n"; @@ -345,6 +379,7 @@ Totals.RegionCoverage += Function.RegionCoverage; Totals.LineCoverage += Function.LineCoverage; Totals.BranchCoverage += Function.BranchCoverage; + Totals.MCDCCoverage += Function.MCDCCoverage; render(Function, DC, OS); } if (Totals.ExecutionCount) { @@ -461,6 +496,12 @@ << column("Missed Branches", FileReportColumns[14], Column::RightAlignment) << column("Cover", FileReportColumns[15], Column::RightAlignment); + if (Options.ShowMCDCSummary) + OS << column("MC/DC Conditions", FileReportColumns[16], + Column::RightAlignment) + << column("Missed Conditions", FileReportColumns[17], + Column::RightAlignment) + << column("Cover", FileReportColumns[18], Column::RightAlignment); OS << "\n"; renderDivider(FileReportColumns, OS); OS << "\n"; Index: llvm/tools/llvm-cov/CoverageSummaryInfo.h =================================================================== --- llvm/tools/llvm-cov/CoverageSummaryInfo.h +++ llvm/tools/llvm-cov/CoverageSummaryInfo.h @@ -142,6 +142,47 @@ } }; +/// Provides information about branches coverage for a function/file. +class MCDCCoverageInfo { + /// The number of Independence Pairs that were covered + size_t CoveredPairs; + + /// The total number of Independence Pairs in a function/file. + size_t NumPairs; + +public: + MCDCCoverageInfo() : CoveredPairs(0), NumPairs(0) {} + + MCDCCoverageInfo(size_t CoveredPairs, size_t NumPairs) + : CoveredPairs(CoveredPairs), NumPairs(NumPairs) { + assert(CoveredPairs <= NumPairs && "Covered Pairs over-counted"); + } + + MCDCCoverageInfo &operator+=(const MCDCCoverageInfo &RHS) { + CoveredPairs += RHS.CoveredPairs; + NumPairs += RHS.NumPairs; + return *this; + } + + void merge(const MCDCCoverageInfo &RHS) { + CoveredPairs = std::max(CoveredPairs, RHS.CoveredPairs); + NumPairs = std::max(NumPairs, RHS.NumPairs); + } + + size_t getCoveredPairs() const { return CoveredPairs; } + + size_t getNumPairs() const { return NumPairs; } + + bool isFullyCovered() const { return CoveredPairs == NumPairs; } + + double getPercentCovered() const { + assert(CoveredPairs <= NumPairs && "Covered Pairs over-counted"); + if (NumPairs == 0) + return 0.0; + return double(CoveredPairs) / double(NumPairs) * 100.0; + } +}; + /// Provides information about function coverage for a file. class FunctionCoverageInfo { /// The number of functions that were executed. @@ -189,6 +230,7 @@ RegionCoverageInfo RegionCoverage; LineCoverageInfo LineCoverage; BranchCoverageInfo BranchCoverage; + MCDCCoverageInfo MCDCCoverage; FunctionCoverageSummary(const std::string &Name) : Name(Name), ExecutionCount(0) {} @@ -196,10 +238,11 @@ FunctionCoverageSummary(const std::string &Name, uint64_t ExecutionCount, const RegionCoverageInfo &RegionCoverage, const LineCoverageInfo &LineCoverage, - const BranchCoverageInfo &BranchCoverage) + const BranchCoverageInfo &BranchCoverage, + const MCDCCoverageInfo &MCDCCoverage) : Name(Name), ExecutionCount(ExecutionCount), RegionCoverage(RegionCoverage), LineCoverage(LineCoverage), - BranchCoverage(BranchCoverage) {} + BranchCoverage(BranchCoverage), MCDCCoverage(MCDCCoverage) {} /// Compute the code coverage summary for the given function coverage /// mapping record. @@ -219,6 +262,7 @@ RegionCoverageInfo RegionCoverage; LineCoverageInfo LineCoverage; BranchCoverageInfo BranchCoverage; + MCDCCoverageInfo MCDCCoverage; FunctionCoverageInfo FunctionCoverage; FunctionCoverageInfo InstantiationCoverage; @@ -229,6 +273,7 @@ LineCoverage += RHS.LineCoverage; FunctionCoverage += RHS.FunctionCoverage; BranchCoverage += RHS.BranchCoverage; + MCDCCoverage += RHS.MCDCCoverage; InstantiationCoverage += RHS.InstantiationCoverage; return *this; } @@ -237,6 +282,7 @@ RegionCoverage += Function.RegionCoverage; LineCoverage += Function.LineCoverage; BranchCoverage += Function.BranchCoverage; + MCDCCoverage += Function.MCDCCoverage; FunctionCoverage.addFunction(/*Covered=*/Function.ExecutionCount > 0); } Index: llvm/tools/llvm-cov/CoverageSummaryInfo.cpp =================================================================== --- llvm/tools/llvm-cov/CoverageSummaryInfo.cpp +++ llvm/tools/llvm-cov/CoverageSummaryInfo.cpp @@ -44,6 +44,17 @@ } } +static void sumMCDCPairs(size_t &NumPairs, size_t &CoveredPairs, + const ArrayRef &Records) { + for (const auto &Record : Records) + for (unsigned c = 0; c < Record.getNumConditions(); c++) { + if (!Record.isCondFolded(c)) + ++NumPairs; + if (Record.isCondIndepPairCovered(c)) + ++CoveredPairs; + } +} + FunctionCoverageSummary FunctionCoverageSummary::get(const CoverageMapping &CM, const coverage::FunctionRecord &Function) { @@ -73,11 +84,15 @@ sumBranches(NumBranches, CoveredBranches, CD.getBranches()); sumBranchExpansions(NumBranches, CoveredBranches, CM, CD.getExpansions()); + size_t NumPairs = 0, CoveredPairs = 0; + sumMCDCPairs(NumPairs, CoveredPairs, CD.getMCDCRecords()); + return FunctionCoverageSummary( Function.Name, Function.ExecutionCount, RegionCoverageInfo(CoveredRegions, NumCodeRegions), LineCoverageInfo(CoveredLines, NumLines), - BranchCoverageInfo(CoveredBranches, NumBranches)); + BranchCoverageInfo(CoveredBranches, NumBranches), + MCDCCoverageInfo(CoveredPairs, NumPairs)); } FunctionCoverageSummary @@ -97,10 +112,12 @@ Summary.RegionCoverage = Summaries[0].RegionCoverage; Summary.LineCoverage = Summaries[0].LineCoverage; Summary.BranchCoverage = Summaries[0].BranchCoverage; + Summary.MCDCCoverage = Summaries[0].MCDCCoverage; for (const auto &FCS : Summaries.drop_front()) { Summary.RegionCoverage.merge(FCS.RegionCoverage); Summary.LineCoverage.merge(FCS.LineCoverage); Summary.BranchCoverage.merge(FCS.BranchCoverage); + Summary.MCDCCoverage.merge(FCS.MCDCCoverage); } return Summary; } Index: llvm/tools/llvm-cov/CoverageViewOptions.h =================================================================== --- llvm/tools/llvm-cov/CoverageViewOptions.h +++ llvm/tools/llvm-cov/CoverageViewOptions.h @@ -30,12 +30,14 @@ bool ShowLineNumbers; bool ShowLineStats; bool ShowRegionMarkers; + bool ShowMCDC; bool ShowBranchCounts; bool ShowBranchPercents; bool ShowExpandedRegions; bool ShowFunctionInstantiations; bool ShowFullFilenames; bool ShowBranchSummary; + bool ShowMCDCSummary; bool ShowRegionSummary; bool ShowInstantiationSummary; bool ExportSummaryOnly; Index: llvm/tools/llvm-cov/SourceCoverageView.h =================================================================== --- llvm/tools/llvm-cov/SourceCoverageView.h +++ llvm/tools/llvm-cov/SourceCoverageView.h @@ -84,6 +84,23 @@ } }; +/// A view that represents one or more MCDC regions on a given source line. +struct MCDCView { + std::vector Records; + std::unique_ptr View; + unsigned Line; + + MCDCView(unsigned Line, ArrayRef Records, + std::unique_ptr View) + : Records(Records), View(std::move(View)), Line(Line) {} + + unsigned getLine() const { return Line; } + + friend bool operator<(const MCDCView &LHS, const MCDCView &RHS) { + return LHS.Line < RHS.Line; + } +}; + /// A file manager that handles format-aware file creation. class CoveragePrinter { public: @@ -160,6 +177,9 @@ /// A container for all branches in the source on display. std::vector BranchSubViews; + /// A container for all MCDC records in the source on display. + std::vector MCDCSubViews; + /// A container for all instantiations (e.g template functions) in the source /// on display. std::vector InstantiationSubViews; @@ -233,6 +253,10 @@ virtual void renderBranchView(raw_ostream &OS, BranchView &BRV, unsigned ViewDepth) = 0; + /// Render an MCDC view. + virtual void renderMCDCView(raw_ostream &OS, MCDCView &BRV, + unsigned ViewDepth) = 0; + /// Render \p Title, a project title if one is available, and the /// created time. virtual void renderTitle(raw_ostream &OS, StringRef CellText) = 0; @@ -283,6 +307,10 @@ void addBranch(unsigned Line, ArrayRef Regions, std::unique_ptr View); + /// Add an MCDC subview to this view. + void addMCDCRecord(unsigned Line, ArrayRef Records, + std::unique_ptr View); + /// Print the code coverage information for a specific portion of a /// source file to the output stream. void print(raw_ostream &OS, bool WholeFile, bool ShowSourceName, Index: llvm/tools/llvm-cov/SourceCoverageView.cpp =================================================================== --- llvm/tools/llvm-cov/SourceCoverageView.cpp +++ llvm/tools/llvm-cov/SourceCoverageView.cpp @@ -174,6 +174,12 @@ BranchSubViews.emplace_back(Line, Regions, std::move(View)); } +void SourceCoverageView::addMCDCRecord( + unsigned Line, ArrayRef Records, + std::unique_ptr View) { + MCDCSubViews.emplace_back(Line, Records, std::move(View)); +} + void SourceCoverageView::addInstantiation( StringRef FunctionName, unsigned Line, std::unique_ptr View) { @@ -199,12 +205,15 @@ llvm::stable_sort(ExpansionSubViews); llvm::stable_sort(InstantiationSubViews); llvm::stable_sort(BranchSubViews); + llvm::stable_sort(MCDCSubViews); auto NextESV = ExpansionSubViews.begin(); auto EndESV = ExpansionSubViews.end(); auto NextISV = InstantiationSubViews.begin(); auto EndISV = InstantiationSubViews.end(); auto NextBRV = BranchSubViews.begin(); auto EndBRV = BranchSubViews.end(); + auto NextMSV = MCDCSubViews.begin(); + auto EndMSV = MCDCSubViews.end(); // Get the coverage information for the file. auto StartSegment = CoverageInfo.begin(); @@ -272,6 +281,11 @@ renderBranchView(OS, *NextBRV, ViewDepth + 1); RenderedSubView = true; } + for (; NextMSV != EndMSV && NextMSV->Line == LI.line_number(); ++NextMSV) { + renderViewDivider(OS, ViewDepth + 1); + renderMCDCView(OS, *NextMSV, ViewDepth + 1); + RenderedSubView = true; + } if (RenderedSubView) renderViewDivider(OS, ViewDepth + 1); renderLineSuffix(OS, ViewDepth); Index: llvm/tools/llvm-cov/SourceCoverageViewHTML.h =================================================================== --- llvm/tools/llvm-cov/SourceCoverageViewHTML.h +++ llvm/tools/llvm-cov/SourceCoverageViewHTML.h @@ -71,6 +71,9 @@ void renderBranchView(raw_ostream &OS, BranchView &BRV, unsigned ViewDepth) override; + void renderMCDCView(raw_ostream &OS, MCDCView &BRV, + unsigned ViewDepth) override; + void renderInstantiationView(raw_ostream &OS, InstantiationView &ISV, unsigned ViewDepth) override; Index: llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp =================================================================== --- llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp +++ llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp @@ -315,6 +315,8 @@ Columns.emplace_back(tag("td", "Region Coverage", "column-entry-bold")); if (Opts.ShowBranchSummary) Columns.emplace_back(tag("td", "Branch Coverage", "column-entry-bold")); + if (Opts.ShowMCDCSummary) + Columns.emplace_back(tag("td", "MC/DC", "column-entry-bold")); OS << tag("tr", join(Columns.begin(), Columns.end(), "")); } @@ -384,6 +386,10 @@ AddCoverageTripleToColumn(FCS.BranchCoverage.getCovered(), FCS.BranchCoverage.getNumBranches(), FCS.BranchCoverage.getPercentCovered()); + if (Opts.ShowMCDCSummary) + AddCoverageTripleToColumn(FCS.MCDCCoverage.getCoveredPairs(), + FCS.MCDCCoverage.getNumPairs(), + FCS.MCDCCoverage.getPercentCovered()); if (IsTotals) OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row-bold"); @@ -722,6 +728,52 @@ OS << EndExpansionDiv; } +void SourceCoverageViewHTML::renderMCDCView(raw_ostream &OS, MCDCView &MRV, + unsigned ViewDepth) { + for (auto &Record : MRV.Records) { + OS << BeginExpansionDiv; + OS << BeginPre; + OS << " MC/DC Decision Region ("; + + // Display Line + Column information. + const CounterMappingRegion &DecisionRegion = Record.getDecisionRegion(); + std::string LineNoStr = utostr(uint64_t(DecisionRegion.LineStart)); + std::string ColNoStr = utostr(uint64_t(DecisionRegion.ColumnStart)); + std::string TargetName = "L" + LineNoStr; + OS << tag("span", + a("#" + TargetName, tag("span", LineNoStr + ":" + ColNoStr), + TargetName), + "line-number") + + ") to ("; + LineNoStr = utostr(uint64_t(DecisionRegion.LineEnd)); + ColNoStr = utostr(uint64_t(DecisionRegion.ColumnEnd)); + OS << tag("span", + a("#" + TargetName, tag("span", LineNoStr + ":" + ColNoStr), + TargetName), + "line-number") + + ")\n\n"; + + // Display MC/DC Information. + OS << " Number of Conditions: " << Record.getNumConditions() << "\n"; + for (unsigned i = 0; i < Record.getNumConditions(); i++) { + OS << " " << Record.getConditionHdrStr(i); + } + OS << "\n"; + OS << " Executed MC/DC Test Vectors:\n\n "; + OS << Record.getTestVectorHdrStr(); + for (unsigned i = 0; i < Record.getNumTestVectors(); i++) + OS << Record.getTestVectorStr(i); + OS << "\n"; + for (unsigned i = 0; i < Record.getNumConditions(); i++) + OS << Record.getCondCoverageStr(i); + OS << " MC/DC Coverage for Expression: "; + OS << format("%0.2f", Record.getPercentCovered()) << "%\n"; + OS << EndPre; + OS << EndExpansionDiv; + } + return; +} + void SourceCoverageViewHTML::renderInstantiationView(raw_ostream &OS, InstantiationView &ISV, unsigned ViewDepth) { Index: llvm/tools/llvm-cov/SourceCoverageViewText.h =================================================================== --- llvm/tools/llvm-cov/SourceCoverageViewText.h +++ llvm/tools/llvm-cov/SourceCoverageViewText.h @@ -62,6 +62,9 @@ void renderBranchView(raw_ostream &OS, BranchView &BRV, unsigned ViewDepth) override; + void renderMCDCView(raw_ostream &OS, MCDCView &BRV, + unsigned ViewDepth) override; + void renderInstantiationView(raw_ostream &OS, InstantiationView &ISV, unsigned ViewDepth) override; Index: llvm/tools/llvm-cov/SourceCoverageViewText.cpp =================================================================== --- llvm/tools/llvm-cov/SourceCoverageViewText.cpp +++ llvm/tools/llvm-cov/SourceCoverageViewText.cpp @@ -270,6 +270,57 @@ } } +void SourceCoverageViewText::renderMCDCView(raw_ostream &OS, MCDCView &MRV, + unsigned ViewDepth) { + for (auto &Record : MRV.Records) { + renderLinePrefix(OS, ViewDepth); + OS << "---> MC/DC Decision Region ("; + // Display Line + Column information. + const CounterMappingRegion &DecisionRegion = Record.getDecisionRegion(); + OS << DecisionRegion.LineStart << ":"; + OS << DecisionRegion.ColumnStart << ") to ("; + OS << DecisionRegion.LineEnd << ":"; + OS << DecisionRegion.ColumnEnd << ")\n"; + renderLinePrefix(OS, ViewDepth); + OS << "\n"; + + // Display MC/DC Information. + renderLinePrefix(OS, ViewDepth); + OS << " Number of Conditions: " << Record.getNumConditions() << "\n"; + for (unsigned i = 0; i < Record.getNumConditions(); i++) { + renderLinePrefix(OS, ViewDepth); + OS << " " << Record.getConditionHdrStr(i); + } + renderLinePrefix(OS, ViewDepth); + OS << "\n"; + renderLinePrefix(OS, ViewDepth); + OS << " Executed MC/DC Test Vectors:\n"; + renderLinePrefix(OS, ViewDepth); + OS << "\n"; + renderLinePrefix(OS, ViewDepth); + OS << " "; + OS << Record.getTestVectorHdrStr(); + for (unsigned i = 0; i < Record.getNumTestVectors(); i++) { + renderLinePrefix(OS, ViewDepth); + OS << Record.getTestVectorStr(i); + } + renderLinePrefix(OS, ViewDepth); + OS << "\n"; + for (unsigned i = 0; i < Record.getNumConditions(); i++) { + renderLinePrefix(OS, ViewDepth); + OS << Record.getCondCoverageStr(i); + } + renderLinePrefix(OS, ViewDepth); + OS << " MC/DC Coverage for Decision: "; + colored_ostream(OS, raw_ostream::RED, + getOptions().Colors && Record.getPercentCovered() < 100.0, + /*Bold=*/false, /*BG=*/true) + << format("%0.2f", Record.getPercentCovered()) << "%\n"; + renderLinePrefix(OS, ViewDepth); + OS << "\n"; + } +} + void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS, InstantiationView &ISV, unsigned ViewDepth) { Index: llvm/unittests/ProfileData/CoverageMappingTest.cpp =================================================================== --- llvm/unittests/ProfileData/CoverageMappingTest.cpp +++ llvm/unittests/ProfileData/CoverageMappingTest.cpp @@ -184,6 +184,23 @@ : CounterMappingRegion::makeRegion(C, FileID, LS, CS, LE, CE)); } + void addMCDCDecisionCMR(unsigned Mask, unsigned NC, StringRef File, + unsigned LS, unsigned CS, unsigned LE, unsigned CE) { + auto &Regions = InputFunctions.back().Regions; + unsigned FileID = getFileIndexForFunction(File); + Regions.push_back(CounterMappingRegion::makeDecisionRegion(Mask, NC, + FileID, LS, CS, LE, CE)); + } + + void addMCDCBranchCMR(Counter C1, Counter C2, unsigned ID, unsigned TrueID, + unsigned FalseID, StringRef File, unsigned LS, + unsigned CS, unsigned LE, unsigned CE) { + auto &Regions = InputFunctions.back().Regions; + unsigned FileID = getFileIndexForFunction(File); + Regions.push_back(CounterMappingRegion::makeBranchRegion(C1, C2, ID, + TrueID, FalseID, FileID, LS, CS, LE, CE)); + } + void addExpansionCMR(StringRef File, StringRef ExpandedFile, unsigned LS, unsigned CS, unsigned LE, unsigned CE) { InputFunctions.back().Regions.push_back(CounterMappingRegion::makeExpansion( @@ -824,6 +841,33 @@ ASSERT_EQ(1U, Names.size()); } +// Test that MCDC bitmasks not associated with any code regions are allowed. +TEST_P(CoverageMappingTest, non_code_region_bitmask) { + // No records in profdata + + startFunction("func", 0x1234); + addCMR(Counter::getCounter(0), "file", 1, 1, 5, 5); + addCMR(Counter::getCounter(1), "file", 1, 1, 5, 5); + addCMR(Counter::getCounter(2), "file", 1, 1, 5, 5); + addCMR(Counter::getCounter(3), "file", 1, 1, 5, 5); + + addMCDCDecisionCMR(0, 2, "file", 7, 1, 7, 6); + addMCDCBranchCMR(Counter::getCounter(0), Counter::getCounter(1), 1, 2, 0, + "file", 7, 2, 7, 3); + addMCDCBranchCMR(Counter::getCounter(2), Counter::getCounter(3), 2, 0, 0, + "file", 7, 4, 7, 5); + + EXPECT_THAT_ERROR(loadCoverageMapping(), Succeeded()); + + std::vector Names; + for (const auto &Func : LoadedCoverage->getCoveredFunctions()) { + Names.push_back(Func.Name); + ASSERT_EQ(2U, Func.CountedBranchRegions.size()); + ASSERT_EQ(1U, Func.MCDCRecords.size()); + } + ASSERT_EQ(1U, Names.size()); +} + TEST_P(CoverageMappingTest, strip_filename_prefix) { ProfileWriter.addRecord({"file1:func", 0x1234, {0}}, Err);