Skip to content

Commit af1463d

Browse files
authored
[clang][dataflow] Add an early-out to flowConditionImplies() / flowConditionAllows(). (#78172)
This saves having to assemble the set of constraints and run the SAT solver in the trivial case of `flowConditionImplies(true)` or `flowConditionAllows(false)`. This is an update / reland of my previous reverted [#77453](#77453). That PR contained a logic bug -- the early-out for `flowConditionAllows()` was wrong because my intuition about the logic was wrong. (In particular, note that `flowConditionImplies(F)` does not imply `flowConditionAllows(F)`, even though this may run counter to intuition.) I've now done what I should have done on the first iteration and added more tests. These pass both with and without my early-outs. This patch is a performance win on the benchmarks for the Crubit nullability checker, except for one slight regression on a relatively short benchmark: ``` name old cpu/op new cpu/op delta BM_PointerAnalysisCopyPointer 68.5µs ± 7% 67.6µs ± 4% ~ (p=0.159 n=18+19) BM_PointerAnalysisIntLoop 173µs ± 3% 162µs ± 4% -6.40% (p=0.000 n=19+20) BM_PointerAnalysisPointerLoop 307µs ± 2% 312µs ± 4% +1.56% (p=0.013 n=18+20) BM_PointerAnalysisBranch 199µs ± 4% 181µs ± 4% -8.81% (p=0.000 n=20+20) BM_PointerAnalysisLoopAndBranch 503µs ± 3% 508µs ± 2% ~ (p=0.081 n=18+19) BM_PointerAnalysisTwoLoops 304µs ± 4% 286µs ± 2% -6.04% (p=0.000 n=19+20) BM_PointerAnalysisJoinFilePath 4.78ms ± 3% 4.54ms ± 4% -4.97% (p=0.000 n=20+20) BM_PointerAnalysisCallInLoop 3.05ms ± 3% 2.90ms ± 4% -5.05% (p=0.000 n=19+20) ``` When running clang-tidy on real-world code, the results are less clear. In three runs, averaged, on an arbitrarily chosen input file, I get 11.60 s of user time without this patch and 11.40 s with it, though with considerable measurement noise (I'm seeing up to 0.2 s of variation between runs). Still, this is a very simple change, and it is a clear win in benchmarks, so I think it is worth making.
1 parent af9f2dc commit af1463d

File tree

3 files changed

+37
-3
lines changed

3 files changed

+37
-3
lines changed

clang/include/clang/Analysis/FlowSensitive/Formula.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ class alignas(const Formula *) Formula {
7575
return static_cast<bool>(Value);
7676
}
7777

78+
bool isLiteral(bool b) const {
79+
return kind() == Literal && static_cast<bool>(Value) == b;
80+
}
81+
7882
ArrayRef<const Formula *> operands() const {
7983
return ArrayRef(reinterpret_cast<Formula *const *>(this + 1),
8084
numOperands(kind()));

clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ Solver::Result DataflowAnalysisContext::querySolver(
174174

175175
bool DataflowAnalysisContext::flowConditionImplies(Atom Token,
176176
const Formula &F) {
177+
if (F.isLiteral(true))
178+
return true;
179+
177180
// Returns true if and only if truth assignment of the flow condition implies
178181
// that `F` is also true. We prove whether or not this property holds by
179182
// reducing the problem to satisfiability checking. In other words, we attempt
@@ -188,6 +191,9 @@ bool DataflowAnalysisContext::flowConditionImplies(Atom Token,
188191

189192
bool DataflowAnalysisContext::flowConditionAllows(Atom Token,
190193
const Formula &F) {
194+
if (F.isLiteral(false))
195+
return false;
196+
191197
llvm::SetVector<const Formula *> Constraints;
192198
Constraints.insert(&arena().makeAtomRef(Token));
193199
Constraints.insert(&F);

clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,34 @@ TEST_F(DataflowAnalysisContextTest, DistinctTopsNotEquivalent) {
3333
EXPECT_FALSE(Context.equivalentFormulas(X.formula(), Y.formula()));
3434
}
3535

36-
TEST_F(DataflowAnalysisContextTest, EmptyFlowCondition) {
36+
TEST_F(DataflowAnalysisContextTest, TautologicalFlowConditionImplies) {
3737
Atom FC = A.makeFlowConditionToken();
38-
auto &C = A.makeAtomRef(A.makeAtom());
39-
EXPECT_FALSE(Context.flowConditionImplies(FC, C));
38+
EXPECT_TRUE(Context.flowConditionImplies(FC, A.makeLiteral(true)));
39+
EXPECT_FALSE(Context.flowConditionImplies(FC, A.makeLiteral(false)));
40+
EXPECT_FALSE(Context.flowConditionImplies(FC, A.makeAtomRef(A.makeAtom())));
41+
}
42+
43+
TEST_F(DataflowAnalysisContextTest, TautologicalFlowConditionAllows) {
44+
Atom FC = A.makeFlowConditionToken();
45+
EXPECT_TRUE(Context.flowConditionAllows(FC, A.makeLiteral(true)));
46+
EXPECT_FALSE(Context.flowConditionAllows(FC, A.makeLiteral(false)));
47+
EXPECT_TRUE(Context.flowConditionAllows(FC, A.makeAtomRef(A.makeAtom())));
48+
}
49+
50+
TEST_F(DataflowAnalysisContextTest, ContradictoryFlowConditionImpliesAnything) {
51+
Atom FC = A.makeFlowConditionToken();
52+
Context.addFlowConditionConstraint(FC, A.makeLiteral(false));
53+
EXPECT_TRUE(Context.flowConditionImplies(FC, A.makeLiteral(true)));
54+
EXPECT_TRUE(Context.flowConditionImplies(FC, A.makeLiteral(false)));
55+
EXPECT_TRUE(Context.flowConditionImplies(FC, A.makeAtomRef(A.makeAtom())));
56+
}
57+
58+
TEST_F(DataflowAnalysisContextTest, ContradictoryFlowConditionAllowsNothing) {
59+
Atom FC = A.makeFlowConditionToken();
60+
Context.addFlowConditionConstraint(FC, A.makeLiteral(false));
61+
EXPECT_FALSE(Context.flowConditionAllows(FC, A.makeLiteral(true)));
62+
EXPECT_FALSE(Context.flowConditionAllows(FC, A.makeLiteral(false)));
63+
EXPECT_FALSE(Context.flowConditionAllows(FC, A.makeAtomRef(A.makeAtom())));
4064
}
4165

4266
TEST_F(DataflowAnalysisContextTest, AddFlowConditionConstraint) {

0 commit comments

Comments
 (0)