Index: clang/include/clang/Basic/CodeGenOptions.def =================================================================== --- clang/include/clang/Basic/CodeGenOptions.def +++ clang/include/clang/Basic/CodeGenOptions.def @@ -223,6 +223,7 @@ ///< enable code coverage analysis. CODEGENOPT(DumpCoverageMapping , 1, 0) ///< Dump the generated coverage mapping ///< regions. +CODEGENOPT(MCDCCoverage , 1, 0) ///< Enable MC/DC code coverage criteria. /// 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 @@ -1376,6 +1376,10 @@ CodeGenOpts<"CoverageMapping">, DefaultFalse, PosFlag, NegFlag, BothFlags<[CoreOption]>>; +defm mcdc_coverage : BoolFOption<"coverage-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 @@ -4560,6 +4560,12 @@ 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.maybeResetMCDCCondBitmap(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 @@ -4568,6 +4574,7 @@ // "FalseBlock" after the increment is done. if (InstrumentRegions && CodeGenFunction::isInstrumentedCondition(E->getRHS())) { + CGF.maybeUpdateMCDCCondBitmap(E->getRHS(), RHSCond); llvm::BasicBlock *FBlock = CGF.createBasicBlock("land.end"); llvm::BasicBlock *RHSBlockCnt = CGF.createBasicBlock("land.rhscnt"); Builder.CreateCondBr(RHSCond, RHSBlockCnt, FBlock); @@ -4577,6 +4584,11 @@ CGF.EmitBlock(FBlock); } + CGF.MCDCLogOpStack.pop_back(); + // If the top of the logical operator nest, update the MCDC bitmap. + if (CGF.MCDCLogOpStack.empty()) + CGF.maybeUpdateMCDCTestVectorBitmap(E); + // ZExt result to int or bool. return Builder.CreateZExtOrBitCast(RHSCond, ResTy, "land.ext"); } @@ -4586,6 +4598,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.maybeResetMCDCCondBitmap(E); + + CGF.MCDCLogOpStack.push_back(E); + llvm::BasicBlock *ContBlock = CGF.createBasicBlock("land.end"); llvm::BasicBlock *RHSBlock = CGF.createBasicBlock("land.rhs"); @@ -4618,6 +4636,7 @@ // condition coverage. if (InstrumentRegions && CodeGenFunction::isInstrumentedCondition(E->getRHS())) { + CGF.maybeUpdateMCDCCondBitmap(E->getRHS(), RHSCond); llvm::BasicBlock *RHSBlockCnt = CGF.createBasicBlock("land.rhscnt"); Builder.CreateCondBr(RHSCond, RHSBlockCnt, ContBlock); CGF.EmitBlock(RHSBlockCnt); @@ -4635,6 +4654,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 bitmap. + if (CGF.MCDCLogOpStack.empty()) + CGF.maybeUpdateMCDCTestVectorBitmap(E); + // Artificial location to preserve the scope information { auto NL = ApplyDebugLocation::CreateArtificial(CGF); @@ -4676,6 +4700,12 @@ 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.maybeResetMCDCCondBitmap(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 @@ -4684,6 +4714,7 @@ // "FalseBlock" after the increment is done. if (InstrumentRegions && CodeGenFunction::isInstrumentedCondition(E->getRHS())) { + CGF.maybeUpdateMCDCCondBitmap(E->getRHS(), RHSCond); llvm::BasicBlock *FBlock = CGF.createBasicBlock("lor.end"); llvm::BasicBlock *RHSBlockCnt = CGF.createBasicBlock("lor.rhscnt"); Builder.CreateCondBr(RHSCond, FBlock, RHSBlockCnt); @@ -4693,6 +4724,11 @@ CGF.EmitBlock(FBlock); } + CGF.MCDCLogOpStack.pop_back(); + // If the top of the logical operator nest, update the MCDC bitmap. + if (CGF.MCDCLogOpStack.empty()) + CGF.maybeUpdateMCDCTestVectorBitmap(E); + // ZExt result to int or bool. return Builder.CreateZExtOrBitCast(RHSCond, ResTy, "lor.ext"); } @@ -4702,6 +4738,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.maybeResetMCDCCondBitmap(E); + + CGF.MCDCLogOpStack.push_back(E); + llvm::BasicBlock *ContBlock = CGF.createBasicBlock("lor.end"); llvm::BasicBlock *RHSBlock = CGF.createBasicBlock("lor.rhs"); @@ -4738,6 +4780,7 @@ // condition coverage. if (InstrumentRegions && CodeGenFunction::isInstrumentedCondition(E->getRHS())) { + CGF.maybeUpdateMCDCCondBitmap(E->getRHS(), RHSCond); llvm::BasicBlock *RHSBlockCnt = CGF.createBasicBlock("lor.rhscnt"); Builder.CreateCondBr(RHSCond, ContBlock, RHSBlockCnt); CGF.EmitBlock(RHSBlockCnt); @@ -4751,6 +4794,11 @@ CGF.EmitBlock(ContBlock); PN->addIncoming(RHSCond, RHSBlock); + CGF.MCDCLogOpStack.pop_back(); + // If the top of the logical operator nest, update the MCDC bitmap. + if (CGF.MCDCLogOpStack.empty()) + CGF.maybeUpdateMCDCTestVectorBitmap(E); + // ZExt result to int. return Builder.CreateZExtOrBitCast(PN, ResTy, "lor.ext"); } @@ -4895,6 +4943,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.maybeResetMCDCCondBitmap(condExpr); + llvm::BasicBlock *LHSBlock = CGF.createBasicBlock("cond.true"); llvm::BasicBlock *RHSBlock = CGF.createBasicBlock("cond.false"); llvm::BasicBlock *ContBlock = CGF.createBasicBlock("cond.end"); @@ -4930,6 +4982,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 bitmap. + if (CGF.MCDCLogOpStack.empty()) + CGF.maybeUpdateMCDCTestVectorBitmap(condExpr); + return PN; } Index: clang/lib/CodeGen/CGStmt.cpp =================================================================== --- clang/lib/CodeGen/CGStmt.cpp +++ clang/lib/CodeGen/CGStmt.cpp @@ -832,7 +832,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 test vector bitmap 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 @@ -287,6 +287,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; @@ -1521,6 +1524,9 @@ CodeGenPGO PGO; + /// Bitmap used by MC/DC to track condition outcomes of a boolean expression. + Address MCDCCondBitmapAddr = Address::invalid(); + /// Calculate branch weights appropriate for PGO data llvm::MDNode *createProfileWeights(uint64_t TrueCount, uint64_t FalseCount) const; @@ -1539,6 +1545,52 @@ PGO.setCurrentStmt(S); } + bool isMCDCCoverageEnabled() const { + 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 maybeCreateMCDCCondBitmap() { + if (isMCDCCoverageEnabled()) { + PGO.emitMCDCParameters(Builder); + MCDCCondBitmapAddr = CreateIRTemp(getContext().UnsignedIntTy, + "mcdc.addr"); + } + } + + bool isBinaryLogicalOp(const Expr *E) const { + const BinaryOperator *BOp = dyn_cast(E->IgnoreParens()); + return (BOp && BOp->isLogicalOp()); + } + + /// Zero-init the MCDC temp value. + void maybeResetMCDCCondBitmap(const Expr *E) { + if (isMCDCCoverageEnabled() && isBinaryLogicalOp(E)) { + PGO.emitMCDCCondBitmapReset(Builder, E, MCDCCondBitmapAddr); + PGO.setCurrentStmt(E); + } + } + + /// Increment the profiler's counter for the given expression by \p StepV. + /// If \p StepV is null, the default increment is 1. + void maybeUpdateMCDCTestVectorBitmap(const Expr *E) { + if (isMCDCCoverageEnabled() && isBinaryLogicalOp(E)) { + PGO.emitMCDCTestVectorBitmapUpdate(Builder, E, MCDCCondBitmapAddr); + PGO.setCurrentStmt(E); + } + } + + /// Update the MCDC temp value with the condition's evaluated result. + void maybeUpdateMCDCCondBitmap(const Expr *E, llvm::Value *Val) { + if (isMCDCCoverageEnabled()) { + PGO.emitMCDCCondBitmapUpdate(Builder, E, MCDCCondBitmapAddr, Val); + PGO.setCurrentStmt(E); + } + } + /// Get the profiler's count for the given statement. uint64_t getProfileCount(const Stmt *S) { return PGO.getStmtCount(S).value_or(0); @@ -4572,6 +4624,9 @@ bool ConstantFoldsToSimpleInteger(const Expr *Cond, llvm::APSInt &Result, bool AllowLabels = false); + /// Ignore parentheses and logical-NOT to track conditions consistently. + static const Expr *stripCond(const Expr *C); + /// isInstrumentedCondition - Determine whether the given condition is an /// instrumentable condition (i.e. no "&&" or "||"). static bool isInstrumentedCondition(const Expr *C); @@ -4594,7 +4649,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 @@ -1245,6 +1245,7 @@ void CodeGenFunction::EmitFunctionBody(const Stmt *Body) { incrementProfileCounter(Body); + maybeCreateMCDCCondBitmap(); if (const CompoundStmt *S = dyn_cast(Body)) EmitCompoundStmtWithoutScope(*S); else @@ -1579,6 +1580,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; @@ -1607,16 +1615,19 @@ return true; } +/// Strip parentheses and simplistic logical-NOT operators. +const Expr *CodeGenFunction::stripCond(const Expr *C) { + while (const UnaryOperator *Op = dyn_cast(C->IgnoreParens())) { + if (Op->getOpcode() != UO_LNot) break; + C = Op->getSubExpr(); + } + return C->IgnoreParens(); +} + /// Determine whether the given condition is an instrumentable condition /// (i.e. no "&&" or "||"). bool CodeGenFunction::isInstrumentedCondition(const Expr *C) { - // Bypass simplistic logical-NOT operator before determining whether the - // condition contains any other logical operator. - if (const UnaryOperator *UnOp = dyn_cast(C->IgnoreParens())) - if (UnOp->getOpcode() == UO_LNot) - C = UnOp->getSubExpr(); - - const BinaryOperator *BOp = dyn_cast(C->IgnoreParens()); + const BinaryOperator *BOp = dyn_cast(stripCond(C)); return (!BOp || !BOp->isLogicalOp()); } @@ -1695,17 +1706,19 @@ /// 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) { +/// \param ConditionalOp Used by MC/DC code coverage to track the result of the +/// ConditionalOperator (ternary) through a recursive call for the operator's +/// LHS and RHS nodes. +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; @@ -1713,8 +1726,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. @@ -1722,8 +1737,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 @@ -1752,11 +1769,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; @@ -1764,8 +1783,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. @@ -1773,10 +1794,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"); @@ -1807,14 +1829,20 @@ RHSCount, LH); eval.end(*this); - + MCDCLogOpStack.pop_back(); return; } } if (const UnaryOperator *CondUOp = dyn_cast(Cond)) { // br(!x, t, f) -> br(x, f, t) - if (CondUOp->getOpcode() == UO_LNot) { + // Avoid doing this optimization when instrumenting a condition for MC/DC. + // LNot is taken as part of the condition for simplicity, and changing its + // sense negatively impacts test vector tracking. + bool MCDCCondition = CGM.getCodeGenOpts().hasProfileClangInstr() && + CGM.getCodeGenOpts().MCDCCoverage && + isInstrumentedCondition(Cond); + if (CondUOp->getOpcode() == UO_LNot && !MCDCCondition) { // Negate the count. uint64_t FalseCount = getCurrentProfileCount() - TrueCount; // The values of the enum are chosen to make this negation possible. @@ -1854,14 +1882,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; @@ -1884,6 +1912,21 @@ 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; + + maybeUpdateMCDCCondBitmap(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 @@ -33,8 +33,11 @@ std::array NumValueSites; unsigned NumRegionCounters; + unsigned MCDCBitmapBytes; uint64_t FunctionHash; std::unique_ptr> RegionCounterMap; + std::unique_ptr> RegionMCDCBitmapMap; + std::unique_ptr> RegionCondIDMap; std::unique_ptr> StmtCountMap; std::unique_ptr ProfRecord; std::vector RegionCounts; @@ -43,7 +46,8 @@ public: CodeGenPGO(CodeGenModule &CGModule) : CGM(CGModule), FuncNameVar(nullptr), NumValueSites({{0}}), - NumRegionCounters(0), FunctionHash(0), CurrentRegionCount(0) {} + NumRegionCounters(0), MCDCBitmapBytes(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 @@ -103,10 +107,18 @@ 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 emitMCDCTestVectorBitmapUpdate(CGBuilderTy &Builder, const Expr *S, + Address MCDCCondBitmapAddr); + void emitMCDCParameters(CGBuilderTy &Builder); + void emitMCDCCondBitmapReset(CGBuilderTy &Builder, const Expr *S, + Address MCDCCondBitmapAddr); + void emitMCDCCondBitmapUpdate(CGBuilderTy &Builder, const Expr *S, + Address MCDCCondBitmapAddr, 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 @@ -161,13 +161,24 @@ PGOHash Hash; /// The map of statements to counters. llvm::DenseMap &CounterMap; + /// The next bitmap byte index to assign. + unsigned NextMCDCBitmapIdx; + /// The map of statements to MC/DC bitmap coverage objects. + llvm::DenseMap &MCDCBitmapMap; + /// Maximum number of supported MC/DC conditions in a boolean expression. + unsigned MCDCMaxCond; /// The profile version. uint64_t ProfileVersion; + /// Diagnostics Engine used to report warnings. + DiagnosticsEngine &Diag; MapRegionCounters(PGOHashVersion HashVersion, uint64_t ProfileVersion, - llvm::DenseMap &CounterMap) + llvm::DenseMap &CounterMap, + llvm::DenseMap &MCDCBitmapMap, + unsigned MCDCMaxCond, DiagnosticsEngine &Diag) : NextCounter(0), Hash(HashVersion), CounterMap(CounterMap), - ProfileVersion(ProfileVersion) {} + NextMCDCBitmapIdx(0), MCDCBitmapMap(MCDCBitmapMap), + MCDCMaxCond(MCDCMaxCond), ProfileVersion(ProfileVersion), Diag(Diag) {} // Blocks and lambdas are handled as separate functions, so we need not // traverse them in the parent context. @@ -207,15 +218,127 @@ 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 bitmap 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, MCDCMaxCond will be set to 0. Do nothing. + if (MCDCMaxCond == 0) + return true; + + /// At the top of the logical operator nest, reset the number of conditions. + if (LogOpStack.empty()) + NumCond = 0; + + if (const Expr *E = dyn_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, MCDCMaxCond will be set to 0. Do nothing. + if (MCDCMaxCond == 0) + return true; + + if (const Expr *E = dyn_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::Warning, + "MC/DC: Unsupported boolean expression; " + "contains an operation with a nested boolean expression. " + "Expression will not be covered."); + Diag.Report(S->getBeginLoc(), DiagID); + return false; + + /// Was the maximum number of conditions encountered? + } else if (NumCond > MCDCMaxCond) { + unsigned DiagID = Diag.getCustomDiagID( + DiagnosticsEngine::Error, + "MC/DC: Unsupported boolean expression; " + "number of conditions (%0) exceeds max (%1)."); + Diag.Report(S->getBeginLoc(), DiagID) << NumCond << MCDCMaxCond; + return false; + + // Otherwise, allocate the number of bytes required for the bitmap + // based on the number of conditions. Must be at least 1-byte long. + } else { + MCDCBitmapMap[BinOp] = NextMCDCBitmapIdx; + unsigned SizeInBits = llvm::alignTo(1L << NumCond, CHAR_BIT); + NextMCDCBitmapIdx += (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 (S->isLogicalOp()) { + if (CodeGenFunction::isInstrumentedCondition(S->getLHS())) { + NumCond++; + } + if (CodeGenFunction::isInstrumentedCondition(S->getRHS())) { + if (ProfileVersion >= llvm::IndexedInstrProf::Version7) { + CounterMap[S->getRHS()] = NextCounter++; + } + NumCond++; + } + } return Base::VisitBinaryOperator(S); } @@ -849,8 +972,16 @@ ProfileVersion = PGOReader->getVersion(); } + // If MC/DC is enabled, set the MaxConditions to the preset value. Otherwise, + // set it to zero. Currently, the max value is hard-coded at 6, but in the + // future, this could be adjusted by way of a command-line option. + unsigned MCDCMaxConditions = (CGM.getCodeGenOpts().MCDCCoverage) ? 6 : 0; + RegionCounterMap.reset(new llvm::DenseMap); - MapRegionCounters Walker(HashVersion, ProfileVersion, *RegionCounterMap); + RegionMCDCBitmapMap.reset(new llvm::DenseMap); + MapRegionCounters Walker(HashVersion, ProfileVersion, *RegionCounterMap, + *RegionMCDCBitmapMap, 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)) @@ -861,6 +992,7 @@ Walker.TraverseDecl(const_cast(CD)); assert(Walker.NextCounter > 0 && "no entry counter mapped for decl"); NumRegionCounters = Walker.NextCounter; + MCDCBitmapBytes = Walker.NextMCDCBitmapIdx; FunctionHash = Walker.Hash.finalize(); } @@ -892,9 +1024,12 @@ 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(), + RegionMCDCBitmapMap.get(), + RegionCondIDMap.get()); MappingGen.emitCounterMapping(D, OS); OS.flush(); @@ -973,6 +1108,108 @@ ArrayRef(Args)); } +bool CodeGenPGO::canEmitMCDCCoverage(const CGBuilderTy &Builder) { + return (CGM.getCodeGenOpts().hasProfileClangInstr() && + CGM.getCodeGenOpts().MCDCCoverage && + Builder.GetInsertBlock()); +} + +void CodeGenPGO::emitMCDCParameters(CGBuilderTy &Builder) { + if (!canEmitMCDCCoverage(Builder) || !RegionMCDCBitmapMap) + return; + + auto *I8PtrTy = llvm::Type::getInt8PtrTy(CGM.getLLVMContext()); + + // Emit intrinsic representing MCDC bitmap 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(MCDCBitmapBytes)}; + Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::instrprof_mcdc_parameters), Args); +} + +void CodeGenPGO::emitMCDCTestVectorBitmapUpdate(CGBuilderTy &Builder, + const Expr *S, + Address MCDCCondBitmapAddr) { + if (!canEmitMCDCCoverage(Builder) || !RegionMCDCBitmapMap) + return; + + S = S->IgnoreParens(); + + auto expr_mcdc_bitmap_map_iterator = RegionMCDCBitmapMap->find(S); + if (expr_mcdc_bitmap_map_iterator == RegionMCDCBitmapMap->end()) + return; + + // Extract the ID of the global bitmap associated with this expression. + unsigned MCDCTestVectorBitmapID = expr_mcdc_bitmap_map_iterator->second; + auto *I8PtrTy = llvm::Type::getInt8PtrTy(CGM.getLLVMContext()); + + // Emit intrinsic responsible for updating the global bitmap corresponding to + // a boolean expression. The index being set is based on the value loaded + // from a pointer to a dedicated temporary value on the stack that is itself + // updated via emitMCDCCondBitmapReset() and emitMCDCCondBitmapUpdate(). The + // index represents an executed test vector. + llvm::Value *Args[5] = {llvm::ConstantExpr::getBitCast(FuncNameVar, I8PtrTy), + Builder.getInt64(FunctionHash), + Builder.getInt32(MCDCBitmapBytes), + Builder.getInt32(MCDCTestVectorBitmapID), + MCDCCondBitmapAddr.getPointer()}; + Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::instrprof_mcdc_tvbitmap_update), Args); +} + +void CodeGenPGO::emitMCDCCondBitmapReset(CGBuilderTy &Builder, const Expr *S, + Address MCDCCondBitmapAddr) { + if (!canEmitMCDCCoverage(Builder) || !RegionMCDCBitmapMap) + return; + + S = S->IgnoreParens(); + + if (RegionMCDCBitmapMap->find(S) == RegionMCDCBitmapMap->end()) + return; + + // Emit intrinsic that resets a dedicated temporary value on the stack to 0. + Builder.CreateStore(Builder.getInt32(0), MCDCCondBitmapAddr); +} + +void CodeGenPGO::emitMCDCCondBitmapUpdate(CGBuilderTy &Builder, const Expr *S, + Address MCDCCondBitmapAddr, + llvm::Value *Val) { + if (!canEmitMCDCCoverage(Builder) || !RegionCondIDMap) + return; + + // Even though, for simplicity, parentheses and unary logical-NOT operators + // are considered part of their underlying condition for both MC/DC and + // branch coverage, the condition IDs themselves are assigned and tracked + // using the underlying condition itself. This is done solely for + // consistency since parentheses and logical-NOTs are ignored when checking + // whether the condition is actually an instrumentable condition. This can + // also make debugging a bit easier. + S = CodeGenFunction::stripCond(S); + + auto expr_mcdc_cond_id_map_iterator = RegionCondIDMap->find(S); + if (expr_mcdc_cond_id_map_iterator == RegionCondIDMap->end()) + return; + + // Extract the ID of the condition we are setting in the bitmap. + unsigned CondID = expr_mcdc_cond_id_map_iterator->second; + 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 bitmap. + llvm::Value *Args[5] = {llvm::ConstantExpr::getBitCast(FuncNameVar, I8PtrTy), + Builder.getInt64(FunctionHash), + Builder.getInt32(CondID - 1), + MCDCCondBitmapAddr.getPointer(), Val}; + Builder.CreateCall(CGM.getIntrinsic( + llvm::Intrinsic::instrprof_mcdc_condbitmap_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 @@ -150,16 +150,22 @@ SourceManager &SM; const LangOptions &LangOpts; llvm::DenseMap *CounterMap; + llvm::DenseMap *MCDCBitmapMap; + 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), + MCDCBitmapMap(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 *MCDCBitmapMap, + llvm::DenseMap *CondIDMap) + : CVM(CVM), SM(SM), LangOpts(LangOpts), CounterMap(CounterMap), + MCDCBitmapMap(MCDCBitmapMap), 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 @@ -95,6 +95,8 @@ } namespace { +using MCDC_Cond_ID = CounterMappingRegion::MCDC_Cond_ID; +using MCDCParameters = CounterMappingRegion::MCDCParameters; /// A region of source code that can be mapped to a counter. class SourceMappingRegion { @@ -104,6 +106,9 @@ /// Secondary Counter used for Branch Regions for "False" branches. std::optional FalseCount; + /// Parameters used for Modified Condition/Decision Coverage + MCDCParameters MCDCParams; + /// The region's starting location. std::optional LocStart; @@ -122,11 +127,18 @@ } SourceMappingRegion(Counter Count, std::optional FalseCount, + MCDCParameters MCDCParams, std::optional LocStart, std::optional LocEnd, bool GapRegion = false) - : Count(Count), FalseCount(FalseCount), LocStart(LocStart), - LocEnd(LocEnd), GapRegion(GapRegion) {} + : Count(Count), FalseCount(FalseCount), MCDCParams(MCDCParams), + LocStart(LocStart), LocEnd(LocEnd), GapRegion(GapRegion) {} + + SourceMappingRegion(MCDCParameters MCDCParams, + std::optional LocStart, + std::optional LocEnd) + : MCDCParams(MCDCParams), LocStart(LocStart), LocEnd(LocEnd), + GapRegion(false) {} const Counter &getCounter() const { return Count; } @@ -163,6 +175,10 @@ void setGap(bool Gap) { GapRegion = Gap; } bool isBranch() const { return FalseCount.has_value(); } + + bool isMCDCDecision() const { return MCDCParams.NumConditions != 0; } + + const MCDCParameters &getMCDCParams() const { return MCDCParams; } }; /// Spelling locations for the start and end of a source region. @@ -454,8 +470,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.getMCDCParams(), *CovFileID, SR.LineStart, + SR.ColumnStart, SR.LineEnd, SR.ColumnEnd)); + } else if (Region.isMCDCDecision()) { + MappingRegions.push_back(CounterMappingRegion::makeDecisionRegion( + Region.getMCDCParams(), *CovFileID, SR.LineStart, SR.ColumnStart, + SR.LineEnd, SR.ColumnEnd)); } else { MappingRegions.push_back(CounterMappingRegion::makeRegion( Region.getCounter(), *CovFileID, SR.LineStart, SR.ColumnStart, @@ -542,6 +563,239 @@ } }; +/// 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 MCDCBitmapMap that was +/// created during the initial AST walk. The presence of a bitmap 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 bitmap that represents the true + /// or false evaluation of that particular condition. + /// + /// NOTE regarding the use of CodeGenFunction::stripCond(). Even though, for + /// simplicity, parentheses and unary logical-NOT operators are considered + /// part of their underlying condition for both MC/DC and branch coverage, the + /// condition IDs themselves are assigned and tracked using the underlying + /// condition itself. This is done solely for consistency since parentheses + /// and logical-NOTs are ignored when checking whether the condition is + /// actually an instrumentable condition. This can also make debugging a bit + /// easier. + +private: + CodeGenModule &CGM; + + std::vector AndRHS; + std::vector OrRHS; + std::vector NestLevel; + llvm::DenseMap &CondIDs; + llvm::DenseMap &MCDCBitmapMap; + MCDC_Cond_ID NextID = 1; + bool NotMapped = false; + + /// Is this a logical-AND operation? + bool isLAnd(const BinaryOperator *E) const { + return E->getOpcode() == BO_LAnd; + } + + /// Push an ID onto the corresponding RHS stack. + void pushRHS(const BinaryOperator *E) { + std::vector &rhs = isLAnd(E) ? AndRHS : OrRHS; + rhs.push_back(CondIDs[CodeGenFunction::stripCond(E->getRHS())]); + } + + /// Pop an ID from the corresponding RHS stack. + void popRHS(const BinaryOperator *E) { + std::vector &rhs = isLAnd(E) ? AndRHS : OrRHS; + if (!rhs.empty()) + rhs.pop_back(); + } + + /// If the expected ID is on top, pop it off the corresponding RHS stack. + void popRHSifTop(const BinaryOperator *E) { + if (!OrRHS.empty() && CondIDs[E] == OrRHS.back()) + OrRHS.pop_back(); + else if (!AndRHS.empty() && CondIDs[E] == AndRHS.back()) + AndRHS.pop_back(); + } + +public: + MCDCCoverageBuilder(CodeGenModule &CGM, + llvm::DenseMap &CondIDMap, + llvm::DenseMap &MCDCBitmapMap) + : CGM(CGM), CondIDs(CondIDMap), MCDCBitmapMap(MCDCBitmapMap) {} + + /// Return the ID of the RHS of the next, upper nest-level logical-OR. + MCDC_Cond_ID getNextLOrCondID() const { + return OrRHS.empty() ? 0 : OrRHS.back(); + } + + /// Return the ID of the RHS of the next, upper nest-level logical-AND. + MCDC_Cond_ID getNextLAndCondID() const { + return AndRHS.empty() ? 0 : AndRHS.back(); + } + + /// Return the ID of a given condition. + MCDC_Cond_ID getCondID(const Expr *Cond) const { + auto I = CondIDs.find(CodeGenFunction::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) + return; + + // If binary expression is disqualified, don't do mapping. + if (NestLevel.empty() && + MCDCBitmapMap.find(CodeGenFunction::stripCond(E)) == MCDCBitmapMap.end()) + NotMapped = true; + + // Push Stmt on 'NestLevel' stack to keep track of nest location. + NestLevel.push_back(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(CodeGenFunction::stripCond(E)) != CondIDs.end()) { + // If Stmt has an ID, assign its ID to LHS + CondIDs[CodeGenFunction::stripCond(E->getLHS())] = CondIDs[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[CodeGenFunction::stripCond(E->getLHS())] = NextID++; + } + + // Assign ID+1 to RHS. + CondIDs[CodeGenFunction::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) { + if (!CGM.getCodeGenOpts().MCDCCoverage) + return 0; + + unsigned TotalConds = 0; + + // Pop Stmt from 'NestLevel' stack. + assert(NestLevel.back() == E); + NestLevel.pop_back(); + + // 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 @@ -550,9 +804,15 @@ /// The map of statements to count values. llvm::DenseMap &CounterMap; + /// The map of statements to bitmap coverage object values. + llvm::DenseMap &MCDCBitmapMap; + /// 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. @@ -589,6 +849,8 @@ return Counter::getCounter(CounterMap[S]); } + unsigned getRegionBitmap(const Stmt *S) { return MCDCBitmapMap[S]; } + /// Push a region onto the stack. /// /// Returns the index on the stack where the region was pushed. This can be @@ -596,7 +858,9 @@ size_t pushRegion(Counter Count, std::optional StartLoc = std::nullopt, std::optional EndLoc = std::nullopt, - std::optional FalseCount = std::nullopt) { + std::optional FalseCount = std::nullopt, + MCDC_Cond_ID ID = 0, MCDC_Cond_ID TrueID = 0, + MCDC_Cond_ID FalseID = 0) { if (StartLoc && !FalseCount) { MostRecentLocation = *StartLoc; @@ -615,7 +879,19 @@ StartLoc = std::nullopt; if (EndLoc && EndLoc->isInvalid()) EndLoc = std::nullopt; - RegionStack.emplace_back(Count, FalseCount, StartLoc, EndLoc); + RegionStack.emplace_back(Count, FalseCount, + MCDCParameters(ID, TrueID, FalseID), + StartLoc, EndLoc); + + return RegionStack.size() - 1; + } + + size_t pushRegion(unsigned BitmapIdx, unsigned Conditions, + std::optional StartLoc = std::nullopt, + std::optional EndLoc = std::nullopt) { + + RegionStack.emplace_back(MCDCParameters(BitmapIdx, Conditions), StartLoc, + EndLoc); return RegionStack.size() - 1; } @@ -746,7 +1022,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, + MCDC_Cond_ID ID = 0, MCDC_Cond_ID TrueID = 0, + MCDC_Cond_ID FalseID = 0) { // Check for NULL conditions. if (!C) return; @@ -764,13 +1042,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 BitmapIdx 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 BitmapIdx, unsigned Conds) { + popRegions(pushRegion(BitmapIdx, Conds, 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, @@ -851,8 +1137,11 @@ // 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(), + MCDCParameters(I.getMCDCParams().ID, I.getMCDCParams().TrueID, + I.getMCDCParams().FalseID), Loc, getEndOfFileOrMacro(Loc), + I.isBranch()); else SourceRegions.emplace_back(I.getCounter(), Loc, getEndOfFileOrMacro(Loc)); @@ -971,9 +1260,13 @@ CounterCoverageMappingBuilder( CoverageMappingModuleGen &CVM, - llvm::DenseMap &CounterMap, SourceManager &SM, + llvm::DenseMap &CounterMap, + llvm::DenseMap &MCDCBitmapMap, + llvm::DenseMap &CondIDMap, SourceManager &SM, const LangOptions &LangOpts) - : CoverageMappingBuilder(CVM, SM, LangOpts), CounterMap(CounterMap) {} + : CoverageMappingBuilder(CVM, SM, LangOpts), CounterMap(CounterMap), + MCDCBitmapMap(MCDCBitmapMap), + MCDCBuilder(CVM.getCodeGenModule(), CondIDMap, MCDCBitmapMap) {} /// Write the mapping data to the output stream void write(llvm::raw_ostream &OS) { @@ -1509,6 +1802,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())); @@ -1517,6 +1813,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, getRegionBitmap(E), NumConds); + // Extract the RHS's Execution Counter. Counter RHSExecCnt = getRegionCounter(E); @@ -1526,13 +1827,30 @@ // Extract the Parent Region Counter. Counter ParentCnt = getRegion().getCounter(); + // Extract the MCDC condition IDs (returns 0 if not needed). + MCDC_Cond_ID NextOrID = MCDCBuilder.getNextLOrCondID(); + MCDC_Cond_ID NextAndID = MCDCBuilder.getNextLAndCondID(); + MCDC_Cond_ID LHSid = MCDCBuilder.getCondID(E->getLHS()); + MCDC_Cond_ID 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); } // Determine whether the right side of OR operation need to be visited. @@ -1546,6 +1864,9 @@ } void VisitBinLOr(const BinaryOperator *E) { + // Keep track of Binary Operator and assign MCDC condition IDs + MCDCBuilder.pushAndAssignIDs(E); + extendRegion(E->getLHS()); Counter OutCount = propagateCounts(getRegion().getCounter(), E->getLHS()); handleFileExit(getEnd(E->getLHS())); @@ -1554,6 +1875,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, getRegionBitmap(E), NumConds); + // Extract the RHS's Execution Counter. Counter RHSExecCnt = getRegionCounter(E); @@ -1567,13 +1893,28 @@ // Extract the Parent Region Counter. Counter ParentCnt = getRegion().getCounter(); + // Extract the MCDC condition IDs (returns 0 if not needed). + MCDC_Cond_ID NextOrID = MCDCBuilder.getNextLOrCondID(); + MCDC_Cond_ID NextAndID = MCDCBuilder.getNextLAndCondID(); + MCDC_Cond_ID LHSid = MCDCBuilder.getCondID(E->getLHS()); + MCDC_Cond_ID 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) { @@ -1613,17 +1954,33 @@ 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.MCDCParams.BitmapIdx; + OS << ", C:" << R.MCDCParams.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.MCDCParams.ID << "," << R.MCDCParams.TrueID; + OS << "," << R.MCDCParams.FalseID << "] "; } if (R.Kind == CounterMappingRegion::ExpansionRegion) @@ -1834,8 +2191,9 @@ void CoverageMappingGen::emitCounterMapping(const Decl *D, llvm::raw_ostream &OS) { - assert(CounterMap); - CounterCoverageMappingBuilder Walker(CVM, *CounterMap, SM, LangOpts); + assert(CounterMap && MCDCBitmapMap); + CounterCoverageMappingBuilder Walker(CVM, *CounterMap, *MCDCBitmapMap, + *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 @@ -826,6 +826,17 @@ CmdArgs.push_back("-fcoverage-mapping"); } + if (Args.hasFlag(options::OPT_fmcdc_coverage, options::OPT_fno_mcdc_coverage, + false)) { + if (!Args.hasFlag(options::OPT_fcoverage_mapping, + options::OPT_fno_coverage_mapping, false)) + D.Diag(clang::diag::err_drv_argument_only_allowed_with) + << "-fcoverage-mcdc" + << "-fcoverage-mapping"; + + CmdArgs.push_back("-fcoverage-mcdc"); + } + if (Arg *A = Args.getLastArg(options::OPT_ffile_compilation_dir_EQ, options::OPT_fcoverage_compilation_dir_EQ)) { if (A->getOption().matches(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 @@ -1388,7 +1388,7 @@ addExportedSymbol(CmdArgs, "_reset_fn_list"); } - // Align __llvm_prf_{cnts,data} sections to the maximum expected page + // Align __llvm_prf_{cnts,bits,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(). @@ -1398,7 +1398,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_bitmap, 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 -fcoverage-mcdc -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 -fcoverage-mcdc -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 -fcoverage-mcdc -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 -fms-extensions -mllvm -emptyline-comment-coverage=false -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping -emit-llvm-only -std=c++23 -triple %itanium_abi_triple -main-file-name if.cpp %s | FileCheck %s +// RUN: %clang_cc1 -fms-extensions -mllvm -emptyline-comment-coverage=false -fcoverage-mcdc -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; } struct S { 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 -fcoverage-mcdc -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 -fcoverage-mcdc -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: %clang_cc1 -triple %itanium_abi_triple -std=c++11 -fcoverage-mcdc -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: warning: 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 -fcoverage-mcdc -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 -fcoverage-mcdc -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 -fcoverage-mcdc -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 --coverage -### %t.o 2> %t.log // RUN: FileCheck -check-prefix=NO_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,8 +1,10 @@ // Make sure instrumentation data from available_externally functions doesn't // get thrown out and are emitted with the expected linkage. // RUN: %clang_cc1 -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 -fcoverage-mcdc -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 +// 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 (ptr @__profc_foo to i64), i64 ptrtoint (ptr @__profd_foo to i64)), {{.*}}, section "__DATA,__llvm_prf_data,regular,live_support", align 8 inline int foo(void) { return 1; } 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 -fcoverage-mcdc | 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-not.c =================================================================== --- /dev/null +++ clang/test/Profile/c-mcdc-not.c @@ -0,0 +1,88 @@ +// RUN: %clang_cc1 -triple %itanium_abi_triple %s -o - -emit-llvm -fprofile-instrument=clang -fcoverage-mapping -fcoverage-mcdc | 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: %[[LNOT:lnot[0-9]*]] = xor i1 %[[BOOL]] +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC-DAG: %[[LAB2:[0-9]+]] = zext i1 %[[LNOT]] 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: %[[LNOT:lnot[0-9]*]] = xor i1 %[[BOOL]] +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC-DAG: %[[LAB2:[0-9]+]] = zext i1 %[[LNOT]] 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: %[[LNOT:lnot[0-9]*]] = xor i1 %[[BOOL]] +// MCDC-DAG: %[[TEMP:mcdc.temp[0-9]*]] = load i32, ptr %mcdc.addr, align 4 +// MCDC-DAG: %[[LAB2:[0-9]+]] = zext i1 %[[LNOT]] 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: clang/test/Profile/c-mcdc.c =================================================================== --- /dev/null +++ clang/test/Profile/c-mcdc.c @@ -0,0 +1,102 @@ +// RUN: %clang_cc1 -triple %itanium_abi_triple %s -o - -emit-llvm -fprofile-instrument=clang -fcoverage-mapping -fcoverage-mcdc | 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 +// RUN: %clang_cc1 -triple %itanium_abi_triple %s -o - -emit-llvm -fprofile-instrument=clang -fcoverage-mapping -fcoverage-mcdc -disable-llvm-passes | FileCheck %s -check-prefix=NOPROFPASS + +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 +// NOPROFPASS-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. +// NOPROFPASS-LABEL: @test( +// NOPROFPASS: call void @llvm.instrprof.mcdc.parameters(ptr @__profn_test, i64 [[HASH:[0-9]+]], i32 8) +// 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. +// NOPROFPASS: call void @llvm.instrprof.mcdc.condbitmap.update(ptr @__profn_test, i64 [[HASH]], i32 0, ptr %mcdc.addr, i1 %tobool{{[0-9]*}}) +// 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. +// NOPROFPASS-LABEL: land.lhs.true: +// NOPROFPASS: call void @llvm.instrprof.mcdc.condbitmap.update(ptr @__profn_test, i64 [[HASH]], i32 2, ptr %mcdc.addr, i1 %tobool{{[0-9]*}}) +// 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. +// NOPROFPASS-LABEL: lor.rhs: +// NOPROFPASS: call void @llvm.instrprof.mcdc.condbitmap.update(ptr @__profn_test, i64 [[HASH]], i32 1, ptr %mcdc.addr, i1 %tobool{{[0-9]*}}) +// 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. +// NOPROFPASS-LABEL: land.lhs.true3: +// NOPROFPASS: call void @llvm.instrprof.mcdc.condbitmap.update(ptr @__profn_test, i64 [[HASH]], i32 4, ptr %mcdc.addr, i1 %tobool{{[0-9]*}}) +// 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. +// NOPROFPASS-LABEL: lor.rhs6: +// NOPROFPASS: call void @llvm.instrprof.mcdc.condbitmap.update(ptr @__profn_test, i64 [[HASH]], i32 3, ptr %mcdc.addr, i1 %tobool{{[0-9]*}}) +// 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. +// NOPROFPASS-LABEL: land.rhs: +// NOPROFPASS: call void @llvm.instrprof.mcdc.condbitmap.update(ptr @__profn_test, i64 [[HASH]], i32 5, ptr %mcdc.addr, i1 %tobool{{[0-9]*}}) +// 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. +// NOPROFPASS-LABEL: lor.end: +// NOPROFPASS: call void @llvm.instrprof.mcdc.tvbitmap.update(ptr @__profn_test, i64 [[HASH]], i32 8, i32 0, ptr %mcdc.addr) +// 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/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 Bitmap Bytes: +// CHECK-NEXT: $1 +// CHECK-NEXT: Bitmap 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; +}