Skip to content

[MC/DC] Nested decision #125403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 31 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a6d4be0
[MC/DC] Update CoverageMapping tests
chapuni Feb 2, 2025
06d0d51
[MC/DC] Make covmap tolerant of nested Decisions
chapuni Feb 2, 2025
d414f29
[MC/DC] Refactor MCDC::State::Decision. NFC.
chapuni Feb 2, 2025
2b37ea4
[MC/DC] Refactor MCDCCoverageBuilder. NFC.
chapuni Feb 2, 2025
8eff226
[MC/DC] Prune MCDCLogOpStack and use CGF.isMCDCDecisionExpr
chapuni Feb 2, 2025
c74e5af
[MC/DC] Create dedicated MCDCCondBitmapAddr for each Decision
chapuni Feb 2, 2025
2f4c8bd
Merge branches 'users/chapuni/mcdc/nest/tests', 'users/chapuni/mcdc/n…
chapuni Feb 2, 2025
37adfad
Merge branch 'users/chapuni/mcdc/nest/bitmapaddr' into users/chapuni/…
chapuni Feb 2, 2025
c56ecc3
[MC/DC] Enable nested expressions
chapuni Feb 2, 2025
f70a6c8
[MC/DC] Handle __builtin_expect as if parenthses
chapuni Feb 2, 2025
f2cf50e
[MC/DC] Enable usage of `!` among `&&` and `||`
chapuni Feb 2, 2025
319eb6c
Merge branches 'users/chapuni/mcdc/nest/lnot' and 'users/chapuni/mcdc…
chapuni Feb 2, 2025
9f6928d
Merge branch 'users/chapuni/mcdc/nest/nest' into users/chapuni/mcdc/n…
chapuni Feb 2, 2025
7c71ae3
Update ReleaseNotes
chapuni Feb 3, 2025
176368a
Update ReleaseNotes
chapuni Feb 3, 2025
af33631
[MC/DC] Introduce `-fmcdc-single-conditions` to include also single c…
chapuni Feb 3, 2025
562ad8c
Merge branches 'users/chapuni/mcdc/nest/single' and 'users/chapuni/mc…
chapuni Feb 3, 2025
079bf69
isValid()
chapuni Mar 1, 2025
39e927c
Add comment
chapuni Mar 1, 2025
7731dbe
Prune "nested" warning.
chapuni Mar 1, 2025
8b35e76
Remove mcdc-single-cond.cpp
chapuni Mar 1, 2025
9a175d0
Merge branch 'main' into users/chapuni/mcdc/nest/tests
chapuni Mar 1, 2025
457cf4b
Merge branch 'users/chapuni/mcdc/nest/tests' into users/chapuni/mcdc/…
chapuni Mar 1, 2025
4d531f3
Prune mcdc-single-cond.cpp
chapuni Mar 1, 2025
d6cb61a
Merge branch 'users/chapuni/mcdc/nest/tests' into users/chapuni/mcdc/…
chapuni Mar 1, 2025
7ee8e06
Merge branch 'users/chapuni/mcdc/nest/mcdcstate' into users/chapuni/m…
chapuni Mar 1, 2025
03c851a
Merge branch 'users/chapuni/mcdc/nest/bitmapaddr' into users/chapuni/…
chapuni Mar 1, 2025
9992251
Revert "Merge branches 'users/chapuni/mcdc/nest/single' and 'users/ch…
chapuni Mar 1, 2025
519ae24
Merge branch 'users/chapuni/mcdc/nest/nest-base' into users/chapuni/m…
chapuni Mar 1, 2025
d7f6a64
Merge branch 'users/chapuni/mcdc/nest/nest' into users/chapuni/mcdc/n…
chapuni Mar 1, 2025
270c7a3
ReleaseNotes.rst fixup
chapuni Mar 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ Improvements to Clang's time-trace
Improvements to Coverage Mapping
--------------------------------

- [MC/DC] Unary logical not `!` among binary operators is recognized
as a part of the expression. (#GH124563)

- [MC/DC] Nested expressions are handled as individual MC/DC expressions.

Bug Fixes in This Version
-------------------------

Expand Down
7 changes: 0 additions & 7 deletions clang/docs/SourceBasedCodeCoverage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -515,13 +515,6 @@ requires 8 test vectors.
Expressions such as ``((a0 && b0) || (a1 && b1) || ...)`` can cause the
number of test vectors to increase exponentially.

Also, if a boolean expression is embedded in the nest of another boolean
expression but separated by a non-logical operator, this is also not supported.
For example, in ``x = (a && b && c && func(d && f))``, the ``d && f`` case
starts a new boolean expression that is separated from the other conditions by
the operator ``func()``. When this is encountered, a warning will be generated
and the boolean expression will not be instrumented.

Switch statements
-----------------

Expand Down
17 changes: 17 additions & 0 deletions clang/include/clang/AST/IgnoreExpr.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,23 @@ inline Expr *IgnoreElidableImplicitConstructorSingleStep(Expr *E) {
return E;
}

inline Expr *IgnoreUOpLNotSingleStep(Expr *E) {
if (auto *UO = dyn_cast<UnaryOperator>(E)) {
if (UO->getOpcode() == UO_LNot)
return UO->getSubExpr();
}
return E;
}

inline Expr *IgnoreBuiltinExpectSingleStep(Expr *E) {
if (auto *CE = dyn_cast<CallExpr>(E)) {
if (const FunctionDecl *FD = CE->getDirectCallee())
if (FD->getBuiltinID() == Builtin::BI__builtin_expect)
return CE->getArg(0);
}
return E;
}

inline Expr *IgnoreImplicitAsWrittenSingleStep(Expr *E) {
if (auto *ICE = dyn_cast<ImplicitCastExpr>(E))
return ICE->getSubExprAsWritten();
Expand Down
40 changes: 14 additions & 26 deletions clang/lib/CodeGen/CGExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5058,11 +5058,9 @@ Value *ScalarExprEmitter::VisitBinLAnd(const BinaryOperator *E) {
CGF.incrementProfileCounter(E);

// If the top of the logical operator nest, reset the MCDC temp to 0.
if (CGF.MCDCLogOpStack.empty())
if (CGF.isMCDCDecisionExpr(E))
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
Expand All @@ -5082,9 +5080,8 @@ Value *ScalarExprEmitter::VisitBinLAnd(const BinaryOperator *E) {
} else
CGF.markStmtMaybeUsed(E->getRHS());

CGF.MCDCLogOpStack.pop_back();
// If the top of the logical operator nest, update the MCDC bitmap.
if (CGF.MCDCLogOpStack.empty())
if (CGF.isMCDCDecisionExpr(E))
CGF.maybeUpdateMCDCTestVectorBitmap(E);

// ZExt result to int or bool.
Expand All @@ -5099,11 +5096,9 @@ Value *ScalarExprEmitter::VisitBinLAnd(const BinaryOperator *E) {
}

// If the top of the logical operator nest, reset the MCDC temp to 0.
if (CGF.MCDCLogOpStack.empty())
if (CGF.isMCDCDecisionExpr(E))
CGF.maybeResetMCDCCondBitmap(E);

CGF.MCDCLogOpStack.push_back(E);

llvm::BasicBlock *ContBlock = CGF.createBasicBlock("land.end");
llvm::BasicBlock *RHSBlock = CGF.createBasicBlock("land.rhs");

Expand Down Expand Up @@ -5154,9 +5149,8 @@ Value *ScalarExprEmitter::VisitBinLAnd(const BinaryOperator *E) {
// 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())
if (CGF.isMCDCDecisionExpr(E))
CGF.maybeUpdateMCDCTestVectorBitmap(E);

// Artificial location to preserve the scope information
Expand Down Expand Up @@ -5201,11 +5195,9 @@ Value *ScalarExprEmitter::VisitBinLOr(const BinaryOperator *E) {
CGF.incrementProfileCounter(E);

// If the top of the logical operator nest, reset the MCDC temp to 0.
if (CGF.MCDCLogOpStack.empty())
if (CGF.isMCDCDecisionExpr(E))
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
Expand All @@ -5225,9 +5217,8 @@ Value *ScalarExprEmitter::VisitBinLOr(const BinaryOperator *E) {
} else
CGF.markStmtMaybeUsed(E->getRHS());

CGF.MCDCLogOpStack.pop_back();
// If the top of the logical operator nest, update the MCDC bitmap.
if (CGF.MCDCLogOpStack.empty())
if (CGF.isMCDCDecisionExpr(E))
CGF.maybeUpdateMCDCTestVectorBitmap(E);

// ZExt result to int or bool.
Expand All @@ -5242,11 +5233,9 @@ Value *ScalarExprEmitter::VisitBinLOr(const BinaryOperator *E) {
}

// If the top of the logical operator nest, reset the MCDC temp to 0.
if (CGF.MCDCLogOpStack.empty())
if (CGF.isMCDCDecisionExpr(E))
CGF.maybeResetMCDCCondBitmap(E);

CGF.MCDCLogOpStack.push_back(E);

llvm::BasicBlock *ContBlock = CGF.createBasicBlock("lor.end");
llvm::BasicBlock *RHSBlock = CGF.createBasicBlock("lor.rhs");

Expand Down Expand Up @@ -5297,9 +5286,8 @@ Value *ScalarExprEmitter::VisitBinLOr(const BinaryOperator *E) {
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())
if (CGF.isMCDCDecisionExpr(E))
CGF.maybeUpdateMCDCTestVectorBitmap(E);

// ZExt result to int.
Expand Down Expand Up @@ -5458,8 +5446,8 @@ VisitAbstractConditionalOperator(const AbstractConditionalOperator *E) {
}

// If the top of the logical operator nest, reset the MCDC temp to 0.
if (CGF.MCDCLogOpStack.empty())
CGF.maybeResetMCDCCondBitmap(condExpr);
if (auto E = CGF.stripCond(condExpr); CGF.isMCDCDecisionExpr(E))
CGF.maybeResetMCDCCondBitmap(E);

llvm::BasicBlock *LHSBlock = CGF.createBasicBlock("cond.true");
llvm::BasicBlock *RHSBlock = CGF.createBasicBlock("cond.false");
Expand All @@ -5474,8 +5462,8 @@ VisitAbstractConditionalOperator(const AbstractConditionalOperator *E) {
// If the top of the logical operator nest, update the MCDC bitmap for the
// ConditionalOperator prior to visiting its LHS and RHS blocks, since they
// may also contain a boolean expression.
if (CGF.MCDCLogOpStack.empty())
CGF.maybeUpdateMCDCTestVectorBitmap(condExpr);
if (auto E = CGF.stripCond(condExpr); CGF.isMCDCDecisionExpr(E))
CGF.maybeUpdateMCDCTestVectorBitmap(E);

if (llvm::EnableSingleByteCoverage)
CGF.incrementProfileCounter(lhsExpr);
Expand All @@ -5494,8 +5482,8 @@ VisitAbstractConditionalOperator(const AbstractConditionalOperator *E) {
// If the top of the logical operator nest, update the MCDC bitmap for the
// ConditionalOperator prior to visiting its LHS and RHS blocks, since they
// may also contain a boolean expression.
if (CGF.MCDCLogOpStack.empty())
CGF.maybeUpdateMCDCTestVectorBitmap(condExpr);
if (auto E = CGF.stripCond(condExpr); CGF.isMCDCDecisionExpr(E))
CGF.maybeUpdateMCDCTestVectorBitmap(E);

if (llvm::EnableSingleByteCoverage)
CGF.incrementProfileCounter(rhsExpr);
Expand Down
29 changes: 12 additions & 17 deletions clang/lib/CodeGen/CodeGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/IgnoreExpr.h"
#include "clang/AST/StmtCXX.h"
#include "clang/AST/StmtObjC.h"
#include "clang/Basic/Builtins.h"
Expand Down Expand Up @@ -1749,12 +1750,14 @@ bool CodeGenFunction::ConstantFoldsToSimpleInteger(const Expr *Cond,

/// Strip parentheses and simplistic logical-NOT operators.
const Expr *CodeGenFunction::stripCond(const Expr *C) {
while (const UnaryOperator *Op = dyn_cast<UnaryOperator>(C->IgnoreParens())) {
if (Op->getOpcode() != UO_LNot)
break;
C = Op->getSubExpr();
while (true) {
const Expr *SC = IgnoreExprNodes(
C, IgnoreParensSingleStep, IgnoreUOpLNotSingleStep,
IgnoreBuiltinExpectSingleStep, IgnoreImplicitCastsSingleStep);
if (C == SC)
return SC;
C = SC;
}
return C->IgnoreParens();
}

/// Determine whether the given condition is an instrumentable condition
Expand Down Expand Up @@ -1852,8 +1855,6 @@ void CodeGenFunction::EmitBranchOnBoolExpr(
if (const BinaryOperator *CondBOp = dyn_cast<BinaryOperator>(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;
Expand All @@ -1863,7 +1864,6 @@ void CodeGenFunction::EmitBranchOnBoolExpr(
incrementProfileCounter(CondBOp);
EmitBranchToCounterBlock(CondBOp->getRHS(), BO_LAnd, TrueBlock,
FalseBlock, TrueCount, LH);
MCDCLogOpStack.pop_back();
return;
}

Expand All @@ -1874,7 +1874,6 @@ void CodeGenFunction::EmitBranchOnBoolExpr(
// br(X && 1) -> br(X).
EmitBranchToCounterBlock(CondBOp->getLHS(), BO_LAnd, TrueBlock,
FalseBlock, TrueCount, LH, CondBOp);
MCDCLogOpStack.pop_back();
return;
}

Expand Down Expand Up @@ -1904,13 +1903,10 @@ void CodeGenFunction::EmitBranchOnBoolExpr(
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;
Expand All @@ -1920,7 +1916,6 @@ void CodeGenFunction::EmitBranchOnBoolExpr(
incrementProfileCounter(CondBOp);
EmitBranchToCounterBlock(CondBOp->getRHS(), BO_LOr, TrueBlock,
FalseBlock, TrueCount, LH);
MCDCLogOpStack.pop_back();
return;
}

Expand All @@ -1931,7 +1926,6 @@ void CodeGenFunction::EmitBranchOnBoolExpr(
// br(X || 0) -> br(X).
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
Expand Down Expand Up @@ -1964,7 +1958,6 @@ void CodeGenFunction::EmitBranchOnBoolExpr(
RHSCount, LH);

eval.end(*this);
MCDCLogOpStack.pop_back();
return;
}
}
Expand Down Expand Up @@ -2049,7 +2042,7 @@ void CodeGenFunction::EmitBranchOnBoolExpr(

// 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
Expand All @@ -2059,7 +2052,9 @@ void CodeGenFunction::EmitBranchOnBoolExpr(
if (ConditionalOp)
MCDCBaseExpr = ConditionalOp;

maybeUpdateMCDCCondBitmap(MCDCBaseExpr, CondV);
if (isMCDCBranchExpr(stripCond(MCDCBaseExpr)) &&
!isMCDCDecisionExpr(stripCond(Cond)))
maybeUpdateMCDCCondBitmap(MCDCBaseExpr, CondV);
}

llvm::MDNode *Weights = nullptr;
Expand Down
25 changes: 14 additions & 11 deletions clang/lib/CodeGen/CodeGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,6 @@ class CodeGenFunction : public CodeGenTypeCache {
/// nest would extend.
SmallVector<llvm::CanonicalLoopInfo *, 4> OMPLoopNestStack;

/// Stack to track the Logical Operator recursion nest for MC/DC.
SmallVector<const BinaryOperator *, 16> MCDCLogOpStack;

/// Stack to track the controlled convergence tokens.
SmallVector<llvm::ConvergenceControlInst *, 4> ConvergenceTokenStack;

Expand Down Expand Up @@ -1671,9 +1668,6 @@ class CodeGenFunction : public CodeGenTypeCache {

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;
Expand Down Expand Up @@ -1712,8 +1706,12 @@ class CodeGenFunction : public CodeGenTypeCache {
void maybeCreateMCDCCondBitmap() {
if (isMCDCCoverageEnabled()) {
PGO.emitMCDCParameters(Builder);
MCDCCondBitmapAddr =
CreateIRTemp(getContext().UnsignedIntTy, "mcdc.addr");

// Set up MCDCCondBitmapAddr for each Decision.
// Note: This doesn't initialize Addrs in invalidated Decisions.
for (auto *MCDCCondBitmapAddr : PGO.getMCDCCondBitmapAddrArray(Builder))
*MCDCCondBitmapAddr =
CreateIRTemp(getContext().UnsignedIntTy, "mcdc.addr");
}
}

Expand All @@ -1722,10 +1720,15 @@ class CodeGenFunction : public CodeGenTypeCache {
return (BOp && BOp->isLogicalOp());
}

bool isMCDCDecisionExpr(const Expr *E) const {
return PGO.isMCDCDecisionExpr(E);
}
bool isMCDCBranchExpr(const Expr *E) const { return PGO.isMCDCBranchExpr(E); }

/// Zero-init the MCDC temp value.
void maybeResetMCDCCondBitmap(const Expr *E) {
if (isMCDCCoverageEnabled() && isBinaryLogicalOp(E)) {
PGO.emitMCDCCondBitmapReset(Builder, E, MCDCCondBitmapAddr);
PGO.emitMCDCCondBitmapReset(Builder, E);
PGO.setCurrentStmt(E);
}
}
Expand All @@ -1734,15 +1737,15 @@ class CodeGenFunction : public CodeGenTypeCache {
/// If \p StepV is null, the default increment is 1.
void maybeUpdateMCDCTestVectorBitmap(const Expr *E) {
if (isMCDCCoverageEnabled() && isBinaryLogicalOp(E)) {
PGO.emitMCDCTestVectorBitmapUpdate(Builder, E, MCDCCondBitmapAddr, *this);
PGO.emitMCDCTestVectorBitmapUpdate(Builder, E, *this);
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, *this);
PGO.emitMCDCCondBitmapUpdate(Builder, E, Val, *this);
PGO.setCurrentStmt(E);
}
}
Expand Down
Loading