
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](https://github.com/llvm/llvm-project/pull/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.
175 lines
6.4 KiB
C++
175 lines
6.4 KiB
C++
//===- unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp ---===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
|
|
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include <memory>
|
|
|
|
namespace {
|
|
|
|
using namespace clang;
|
|
using namespace dataflow;
|
|
|
|
class DataflowAnalysisContextTest : public ::testing::Test {
|
|
protected:
|
|
DataflowAnalysisContextTest()
|
|
: Context(std::make_unique<WatchedLiteralsSolver>()), A(Context.arena()) {
|
|
}
|
|
|
|
DataflowAnalysisContext Context;
|
|
Arena &A;
|
|
};
|
|
|
|
TEST_F(DataflowAnalysisContextTest, DistinctTopsNotEquivalent) {
|
|
auto &X = A.makeTopValue();
|
|
auto &Y = A.makeTopValue();
|
|
EXPECT_FALSE(Context.equivalentFormulas(X.formula(), Y.formula()));
|
|
}
|
|
|
|
TEST_F(DataflowAnalysisContextTest, TautologicalFlowConditionImplies) {
|
|
Atom FC = A.makeFlowConditionToken();
|
|
EXPECT_TRUE(Context.flowConditionImplies(FC, A.makeLiteral(true)));
|
|
EXPECT_FALSE(Context.flowConditionImplies(FC, A.makeLiteral(false)));
|
|
EXPECT_FALSE(Context.flowConditionImplies(FC, A.makeAtomRef(A.makeAtom())));
|
|
}
|
|
|
|
TEST_F(DataflowAnalysisContextTest, TautologicalFlowConditionAllows) {
|
|
Atom FC = A.makeFlowConditionToken();
|
|
EXPECT_TRUE(Context.flowConditionAllows(FC, A.makeLiteral(true)));
|
|
EXPECT_FALSE(Context.flowConditionAllows(FC, A.makeLiteral(false)));
|
|
EXPECT_TRUE(Context.flowConditionAllows(FC, A.makeAtomRef(A.makeAtom())));
|
|
}
|
|
|
|
TEST_F(DataflowAnalysisContextTest, ContradictoryFlowConditionImpliesAnything) {
|
|
Atom FC = A.makeFlowConditionToken();
|
|
Context.addFlowConditionConstraint(FC, A.makeLiteral(false));
|
|
EXPECT_TRUE(Context.flowConditionImplies(FC, A.makeLiteral(true)));
|
|
EXPECT_TRUE(Context.flowConditionImplies(FC, A.makeLiteral(false)));
|
|
EXPECT_TRUE(Context.flowConditionImplies(FC, A.makeAtomRef(A.makeAtom())));
|
|
}
|
|
|
|
TEST_F(DataflowAnalysisContextTest, ContradictoryFlowConditionAllowsNothing) {
|
|
Atom FC = A.makeFlowConditionToken();
|
|
Context.addFlowConditionConstraint(FC, A.makeLiteral(false));
|
|
EXPECT_FALSE(Context.flowConditionAllows(FC, A.makeLiteral(true)));
|
|
EXPECT_FALSE(Context.flowConditionAllows(FC, A.makeLiteral(false)));
|
|
EXPECT_FALSE(Context.flowConditionAllows(FC, A.makeAtomRef(A.makeAtom())));
|
|
}
|
|
|
|
TEST_F(DataflowAnalysisContextTest, AddFlowConditionConstraint) {
|
|
Atom FC = A.makeFlowConditionToken();
|
|
auto &C = A.makeAtomRef(A.makeAtom());
|
|
Context.addFlowConditionConstraint(FC, C);
|
|
EXPECT_TRUE(Context.flowConditionImplies(FC, C));
|
|
}
|
|
|
|
TEST_F(DataflowAnalysisContextTest, AddInvariant) {
|
|
Atom FC = A.makeFlowConditionToken();
|
|
auto &C = A.makeAtomRef(A.makeAtom());
|
|
Context.addInvariant(C);
|
|
EXPECT_TRUE(Context.flowConditionImplies(FC, C));
|
|
}
|
|
|
|
TEST_F(DataflowAnalysisContextTest, InvariantAndFCConstraintInteract) {
|
|
Atom FC = A.makeFlowConditionToken();
|
|
auto &C = A.makeAtomRef(A.makeAtom());
|
|
auto &D = A.makeAtomRef(A.makeAtom());
|
|
Context.addInvariant(A.makeImplies(C, D));
|
|
Context.addFlowConditionConstraint(FC, C);
|
|
EXPECT_TRUE(Context.flowConditionImplies(FC, D));
|
|
}
|
|
|
|
TEST_F(DataflowAnalysisContextTest, ForkFlowCondition) {
|
|
Atom FC1 = A.makeFlowConditionToken();
|
|
auto &C1 = A.makeAtomRef(A.makeAtom());
|
|
Context.addFlowConditionConstraint(FC1, C1);
|
|
|
|
// Forked flow condition inherits the constraints of its parent flow
|
|
// condition.
|
|
Atom FC2 = Context.forkFlowCondition(FC1);
|
|
EXPECT_TRUE(Context.flowConditionImplies(FC2, C1));
|
|
|
|
// Adding a new constraint to the forked flow condition does not affect its
|
|
// parent flow condition.
|
|
auto &C2 = A.makeAtomRef(A.makeAtom());
|
|
Context.addFlowConditionConstraint(FC2, C2);
|
|
EXPECT_TRUE(Context.flowConditionImplies(FC2, C2));
|
|
EXPECT_FALSE(Context.flowConditionImplies(FC1, C2));
|
|
}
|
|
|
|
TEST_F(DataflowAnalysisContextTest, JoinFlowConditions) {
|
|
auto &C1 = A.makeAtomRef(A.makeAtom());
|
|
auto &C2 = A.makeAtomRef(A.makeAtom());
|
|
auto &C3 = A.makeAtomRef(A.makeAtom());
|
|
|
|
Atom FC1 = A.makeFlowConditionToken();
|
|
Context.addFlowConditionConstraint(FC1, C1);
|
|
Context.addFlowConditionConstraint(FC1, C3);
|
|
|
|
Atom FC2 = A.makeFlowConditionToken();
|
|
Context.addFlowConditionConstraint(FC2, C2);
|
|
Context.addFlowConditionConstraint(FC2, C3);
|
|
|
|
Atom FC3 = Context.joinFlowConditions(FC1, FC2);
|
|
EXPECT_FALSE(Context.flowConditionImplies(FC3, C1));
|
|
EXPECT_FALSE(Context.flowConditionImplies(FC3, C2));
|
|
EXPECT_TRUE(Context.flowConditionImplies(FC3, C3));
|
|
}
|
|
|
|
TEST_F(DataflowAnalysisContextTest, EquivBoolVals) {
|
|
auto &X = A.makeAtomRef(A.makeAtom());
|
|
auto &Y = A.makeAtomRef(A.makeAtom());
|
|
auto &Z = A.makeAtomRef(A.makeAtom());
|
|
auto &True = A.makeLiteral(true);
|
|
auto &False = A.makeLiteral(false);
|
|
|
|
// X == X
|
|
EXPECT_TRUE(Context.equivalentFormulas(X, X));
|
|
// X != Y
|
|
EXPECT_FALSE(Context.equivalentFormulas(X, Y));
|
|
|
|
// !X != X
|
|
EXPECT_FALSE(Context.equivalentFormulas(A.makeNot(X), X));
|
|
// !(!X) = X
|
|
EXPECT_TRUE(Context.equivalentFormulas(A.makeNot(A.makeNot(X)), X));
|
|
|
|
// (X || X) == X
|
|
EXPECT_TRUE(Context.equivalentFormulas(A.makeOr(X, X), X));
|
|
// (X || Y) != X
|
|
EXPECT_FALSE(Context.equivalentFormulas(A.makeOr(X, Y), X));
|
|
// (X || True) == True
|
|
EXPECT_TRUE(Context.equivalentFormulas(A.makeOr(X, True), True));
|
|
// (X || False) == X
|
|
EXPECT_TRUE(Context.equivalentFormulas(A.makeOr(X, False), X));
|
|
|
|
// (X && X) == X
|
|
EXPECT_TRUE(Context.equivalentFormulas(A.makeAnd(X, X), X));
|
|
// (X && Y) != X
|
|
EXPECT_FALSE(Context.equivalentFormulas(A.makeAnd(X, Y), X));
|
|
// (X && True) == X
|
|
EXPECT_TRUE(Context.equivalentFormulas(A.makeAnd(X, True), X));
|
|
// (X && False) == False
|
|
EXPECT_TRUE(Context.equivalentFormulas(A.makeAnd(X, False), False));
|
|
|
|
// (X || Y) == (Y || X)
|
|
EXPECT_TRUE(Context.equivalentFormulas(A.makeOr(X, Y), A.makeOr(Y, X)));
|
|
// (X && Y) == (Y && X)
|
|
EXPECT_TRUE(Context.equivalentFormulas(A.makeAnd(X, Y), A.makeAnd(Y, X)));
|
|
|
|
// ((X || Y) || Z) == (X || (Y || Z))
|
|
EXPECT_TRUE(Context.equivalentFormulas(A.makeOr(A.makeOr(X, Y), Z),
|
|
A.makeOr(X, A.makeOr(Y, Z))));
|
|
// ((X && Y) && Z) == (X && (Y && Z))
|
|
EXPECT_TRUE(Context.equivalentFormulas(A.makeAnd(A.makeAnd(X, Y), Z),
|
|
A.makeAnd(X, A.makeAnd(Y, Z))));
|
|
}
|
|
|
|
} // namespace
|