Skip to content

Commit

Permalink
Reapply "[dataflow] use true/false literals in formulas, rather than …
Browse files Browse the repository at this point in the history
…variables"

This reverts commit 3353f7d.

Fixed test bug (unspecified order of arg evaluation)
  • Loading branch information
sam-mccall committed Oct 19, 2023
1 parent d404130 commit 7338eb5
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 97 deletions.
10 changes: 5 additions & 5 deletions clang/include/clang/Analysis/FlowSensitive/Arena.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ namespace clang::dataflow {
/// For example, `Value`, `StorageLocation`, `Atom`, and `Formula`.
class Arena {
public:
Arena() : True(makeAtom()), False(makeAtom()) {}
Arena()
: True(Formula::create(Alloc, Formula::Literal, {}, 1)),
False(Formula::create(Alloc, Formula::Literal, {}, 0)) {}
Arena(const Arena &) = delete;
Arena &operator=(const Arena &) = delete;

Expand Down Expand Up @@ -106,9 +108,7 @@ class Arena {
const Formula &makeAtomRef(Atom A);

/// Returns a formula for a literal true/false.
const Formula &makeLiteral(bool Value) {
return makeAtomRef(Value ? True : False);
}
const Formula &makeLiteral(bool Value) { return Value ? True : False; }

// Parses a formula from its textual representation.
// This may refer to atoms that were not produced by makeAtom() yet!
Expand Down Expand Up @@ -144,7 +144,7 @@ class Arena {
llvm::DenseMap<const Formula *, BoolValue *> FormulaValues;
unsigned NextAtom = 0;

Atom True, False;
const Formula &True, &False;
};

} // namespace clang::dataflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,9 +473,8 @@ class Environment {

/// Returns a symbolic boolean value that models a boolean literal equal to
/// `Value`
AtomicBoolValue &getBoolLiteralValue(bool Value) const {
return cast<AtomicBoolValue>(
arena().makeBoolValue(arena().makeLiteral(Value)));
BoolValue &getBoolLiteralValue(bool Value) const {
return arena().makeBoolValue(arena().makeLiteral(Value));
}

/// Returns an atomic boolean value.
Expand Down
15 changes: 11 additions & 4 deletions clang/include/clang/Analysis/FlowSensitive/Formula.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ class alignas(const Formula *) Formula {
/// A reference to an atomic boolean variable.
/// We name these e.g. "V3", where 3 == atom identity == Value.
AtomRef,
// FIXME: add const true/false rather than modeling them as variables
/// Constant true or false.
Literal,

Not, /// True if its only operand is false

Expand All @@ -69,6 +70,11 @@ class alignas(const Formula *) Formula {
return static_cast<Atom>(Value);
}

bool literal() const {
assert(kind() == Literal);
return static_cast<bool>(Value);
}

ArrayRef<const Formula *> operands() const {
return ArrayRef(reinterpret_cast<Formula *const *>(this + 1),
numOperands(kind()));
Expand All @@ -81,9 +87,9 @@ class alignas(const Formula *) Formula {
void print(llvm::raw_ostream &OS, const AtomNames * = nullptr) const;

// Allocate Formulas using Arena rather than calling this function directly.
static Formula &create(llvm::BumpPtrAllocator &Alloc, Kind K,
ArrayRef<const Formula *> Operands,
unsigned Value = 0);
static const Formula &create(llvm::BumpPtrAllocator &Alloc, Kind K,
ArrayRef<const Formula *> Operands,
unsigned Value = 0);

private:
Formula() = default;
Expand All @@ -93,6 +99,7 @@ class alignas(const Formula *) Formula {
static unsigned numOperands(Kind K) {
switch (K) {
case AtomRef:
case Literal:
return 0;
case Not:
return 1;
Expand Down
100 changes: 60 additions & 40 deletions clang/lib/Analysis/FlowSensitive/Arena.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,63 +22,83 @@ canonicalFormulaPair(const Formula &LHS, const Formula &RHS) {
return Res;
}

const Formula &Arena::makeAtomRef(Atom A) {
auto [It, Inserted] = AtomRefs.try_emplace(A);
template <class Key, class ComputeFunc>
const Formula &cached(llvm::DenseMap<Key, const Formula *> &Cache, Key K,
ComputeFunc &&Compute) {
auto [It, Inserted] = Cache.try_emplace(std::forward<Key>(K));
if (Inserted)
It->second =
&Formula::create(Alloc, Formula::AtomRef, {}, static_cast<unsigned>(A));
It->second = Compute();
return *It->second;
}

const Formula &Arena::makeAnd(const Formula &LHS, const Formula &RHS) {
if (&LHS == &RHS)
return LHS;
const Formula &Arena::makeAtomRef(Atom A) {
return cached(AtomRefs, A, [&] {
return &Formula::create(Alloc, Formula::AtomRef, {},
static_cast<unsigned>(A));
});
}

auto [It, Inserted] =
Ands.try_emplace(canonicalFormulaPair(LHS, RHS), nullptr);
if (Inserted)
It->second = &Formula::create(Alloc, Formula::And, {&LHS, &RHS});
return *It->second;
const Formula &Arena::makeAnd(const Formula &LHS, const Formula &RHS) {
return cached(Ands, canonicalFormulaPair(LHS, RHS), [&] {
if (&LHS == &RHS)
return &LHS;
if (LHS.kind() == Formula::Literal)
return LHS.literal() ? &RHS : &LHS;
if (RHS.kind() == Formula::Literal)
return RHS.literal() ? &LHS : &RHS;

return &Formula::create(Alloc, Formula::And, {&LHS, &RHS});
});
}

const Formula &Arena::makeOr(const Formula &LHS, const Formula &RHS) {
if (&LHS == &RHS)
return LHS;

auto [It, Inserted] =
Ors.try_emplace(canonicalFormulaPair(LHS, RHS), nullptr);
if (Inserted)
It->second = &Formula::create(Alloc, Formula::Or, {&LHS, &RHS});
return *It->second;
return cached(Ors, canonicalFormulaPair(LHS, RHS), [&] {
if (&LHS == &RHS)
return &LHS;
if (LHS.kind() == Formula::Literal)
return LHS.literal() ? &LHS : &RHS;
if (RHS.kind() == Formula::Literal)
return RHS.literal() ? &RHS : &LHS;

return &Formula::create(Alloc, Formula::Or, {&LHS, &RHS});
});
}

const Formula &Arena::makeNot(const Formula &Val) {
auto [It, Inserted] = Nots.try_emplace(&Val, nullptr);
if (Inserted)
It->second = &Formula::create(Alloc, Formula::Not, {&Val});
return *It->second;
return cached(Nots, &Val, [&] {
if (Val.kind() == Formula::Not)
return Val.operands()[0];
if (Val.kind() == Formula::Literal)
return &makeLiteral(!Val.literal());

return &Formula::create(Alloc, Formula::Not, {&Val});
});
}

const Formula &Arena::makeImplies(const Formula &LHS, const Formula &RHS) {
if (&LHS == &RHS)
return makeLiteral(true);

auto [It, Inserted] =
Implies.try_emplace(std::make_pair(&LHS, &RHS), nullptr);
if (Inserted)
It->second = &Formula::create(Alloc, Formula::Implies, {&LHS, &RHS});
return *It->second;
return cached(Implies, std::make_pair(&LHS, &RHS), [&] {
if (&LHS == &RHS)
return &makeLiteral(true);
if (LHS.kind() == Formula::Literal)
return LHS.literal() ? &RHS : &makeLiteral(true);
if (RHS.kind() == Formula::Literal)
return RHS.literal() ? &RHS : &makeNot(LHS);

return &Formula::create(Alloc, Formula::Implies, {&LHS, &RHS});
});
}

const Formula &Arena::makeEquals(const Formula &LHS, const Formula &RHS) {
if (&LHS == &RHS)
return makeLiteral(true);

auto [It, Inserted] =
Equals.try_emplace(canonicalFormulaPair(LHS, RHS), nullptr);
if (Inserted)
It->second = &Formula::create(Alloc, Formula::Equal, {&LHS, &RHS});
return *It->second;
return cached(Equals, canonicalFormulaPair(LHS, RHS), [&] {
if (&LHS == &RHS)
return &makeLiteral(true);
if (LHS.kind() == Formula::Literal)
return LHS.literal() ? &RHS : &makeNot(RHS);
if (RHS.kind() == Formula::Literal)
return RHS.literal() ? &LHS : &makeNot(LHS);

return &Formula::create(Alloc, Formula::Equal, {&LHS, &RHS});
});
}

IntegerValue &Arena::makeIntLiteral(llvm::APInt Value) {
Expand Down
9 changes: 1 addition & 8 deletions clang/lib/Analysis/FlowSensitive/DataflowAnalysisContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,6 @@ DataflowAnalysisContext::joinFlowConditions(Atom FirstToken,

Solver::Result DataflowAnalysisContext::querySolver(
llvm::SetVector<const Formula *> Constraints) {
Constraints.insert(&arena().makeLiteral(true));
Constraints.insert(&arena().makeNot(arena().makeLiteral(false)));
return S->solve(Constraints.getArrayRef());
}

Expand Down Expand Up @@ -213,13 +211,8 @@ void DataflowAnalysisContext::dumpFlowCondition(Atom Token,
Constraints.insert(&arena().makeAtomRef(Token));
addTransitiveFlowConditionConstraints(Token, Constraints);

// TODO: have formulas know about true/false directly instead
Atom True = arena().makeLiteral(true).getAtom();
Atom False = arena().makeLiteral(false).getAtom();
Formula::AtomNames Names = {{False, "false"}, {True, "true"}};

for (const auto *Constraint : Constraints) {
Constraint->print(OS, &Names);
Constraint->print(OS);
OS << "\n";
}
}
Expand Down
17 changes: 14 additions & 3 deletions clang/lib/Analysis/FlowSensitive/Formula.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@

namespace clang::dataflow {

Formula &Formula::create(llvm::BumpPtrAllocator &Alloc, Kind K,
ArrayRef<const Formula *> Operands, unsigned Value) {
const Formula &Formula::create(llvm::BumpPtrAllocator &Alloc, Kind K,
ArrayRef<const Formula *> Operands,
unsigned Value) {
assert(Operands.size() == numOperands(K));
if (Value != 0) // Currently, formulas have values or operands, not both.
assert(numOperands(K) == 0);
Expand All @@ -38,6 +39,7 @@ Formula &Formula::create(llvm::BumpPtrAllocator &Alloc, Kind K,
static llvm::StringLiteral sigil(Formula::Kind K) {
switch (K) {
case Formula::AtomRef:
case Formula::Literal:
return "";
case Formula::Not:
return "!";
Expand All @@ -62,7 +64,16 @@ void Formula::print(llvm::raw_ostream &OS, const AtomNames *Names) const {

switch (numOperands(kind())) {
case 0:
OS << getAtom();
switch (kind()) {
case AtomRef:
OS << getAtom();
break;
case Literal:
OS << (literal() ? "true" : "false");
break;
default:
llvm_unreachable("unhandled formula kind");
}
break;
case 1:
OS << sigil(kind());
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ CNFFormula buildCNF(const llvm::ArrayRef<const Formula *> &Vals) {
switch (Val->kind()) {
case Formula::AtomRef:
break;
case Formula::Literal:
CNF.addClause(Val->literal() ? posLit(Var) : negLit(Var));
break;
case Formula::And: {
const Variable LHS = GetVar(Val->operands()[0]);
const Variable RHS = GetVar(Val->operands()[1]);
Expand Down
55 changes: 31 additions & 24 deletions clang/unittests/Analysis/FlowSensitive/ArenaTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,6 @@ TEST_F(ArenaTest, CreateTopBoolValueReturnsDistinctValues) {
EXPECT_NE(&X, &Y);
}

TEST_F(ArenaTest, GetOrCreateConjunctionReturnsSameExprGivenSameArgs) {
auto &X = A.makeAtomRef(A.makeAtom());
auto &XAndX = A.makeAnd(X, X);
EXPECT_EQ(&XAndX, &X);
}

TEST_F(ArenaTest, GetOrCreateConjunctionReturnsSameExprOnSubsequentCalls) {
auto &X = A.makeAtomRef(A.makeAtom());
auto &Y = A.makeAtomRef(A.makeAtom());
Expand All @@ -55,12 +49,6 @@ TEST_F(ArenaTest, GetOrCreateConjunctionReturnsSameExprOnSubsequentCalls) {
EXPECT_NE(&XAndY1, &XAndZ);
}

TEST_F(ArenaTest, GetOrCreateDisjunctionReturnsSameExprGivenSameArgs) {
auto &X = A.makeAtomRef(A.makeAtom());
auto &XOrX = A.makeOr(X, X);
EXPECT_EQ(&XOrX, &X);
}

TEST_F(ArenaTest, GetOrCreateDisjunctionReturnsSameExprOnSubsequentCalls) {
auto &X = A.makeAtomRef(A.makeAtom());
auto &Y = A.makeAtomRef(A.makeAtom());
Expand All @@ -86,12 +74,6 @@ TEST_F(ArenaTest, GetOrCreateNegationReturnsSameExprOnSubsequentCalls) {
EXPECT_NE(&NotX1, &NotY);
}

TEST_F(ArenaTest, GetOrCreateImplicationReturnsTrueGivenSameArgs) {
auto &X = A.makeAtomRef(A.makeAtom());
auto &XImpliesX = A.makeImplies(X, X);
EXPECT_EQ(&XImpliesX, &A.makeLiteral(true));
}

TEST_F(ArenaTest, GetOrCreateImplicationReturnsSameExprOnSubsequentCalls) {
auto &X = A.makeAtomRef(A.makeAtom());
auto &Y = A.makeAtomRef(A.makeAtom());
Expand All @@ -107,12 +89,6 @@ TEST_F(ArenaTest, GetOrCreateImplicationReturnsSameExprOnSubsequentCalls) {
EXPECT_NE(&XImpliesY1, &XImpliesZ);
}

TEST_F(ArenaTest, GetOrCreateIffReturnsTrueGivenSameArgs) {
auto &X = A.makeAtomRef(A.makeAtom());
auto &XIffX = A.makeEquals(X, X);
EXPECT_EQ(&XIffX, &A.makeLiteral(true));
}

TEST_F(ArenaTest, GetOrCreateIffReturnsSameExprOnSubsequentCalls) {
auto &X = A.makeAtomRef(A.makeAtom());
auto &Y = A.makeAtomRef(A.makeAtom());
Expand Down Expand Up @@ -181,5 +157,36 @@ V1 V2
^)"));
}

TEST_F(ArenaTest, IdentitySimplification) {
auto &X = A.makeAtomRef(A.makeAtom());

EXPECT_EQ(&X, &A.makeAnd(X, X));
EXPECT_EQ(&X, &A.makeOr(X, X));
EXPECT_EQ(&A.makeLiteral(true), &A.makeImplies(X, X));
EXPECT_EQ(&A.makeLiteral(true), &A.makeEquals(X, X));
EXPECT_EQ(&X, &A.makeNot(A.makeNot(X)));
}

TEST_F(ArenaTest, LiteralSimplification) {
auto &X = A.makeAtomRef(A.makeAtom());

EXPECT_EQ(&X, &A.makeAnd(X, A.makeLiteral(true)));
EXPECT_EQ(&A.makeLiteral(false), &A.makeAnd(X, A.makeLiteral(false)));

EXPECT_EQ(&A.makeLiteral(true), &A.makeOr(X, A.makeLiteral(true)));
EXPECT_EQ(&X, &A.makeOr(X, A.makeLiteral(false)));

EXPECT_EQ(&A.makeLiteral(true), &A.makeImplies(X, A.makeLiteral(true)));
EXPECT_EQ(&A.makeNot(X), &A.makeImplies(X, A.makeLiteral(false)));
EXPECT_EQ(&X, &A.makeImplies(A.makeLiteral(true), X));
EXPECT_EQ(&A.makeLiteral(true), &A.makeImplies(A.makeLiteral(false), X));

EXPECT_EQ(&X, &A.makeEquals(X, A.makeLiteral(true)));
EXPECT_EQ(&A.makeNot(X), &A.makeEquals(X, A.makeLiteral(false)));

EXPECT_EQ(&A.makeLiteral(false), &A.makeNot(A.makeLiteral(true)));
EXPECT_EQ(&A.makeLiteral(true), &A.makeNot(A.makeLiteral(false)));
}

} // namespace
} // namespace clang::dataflow

0 comments on commit 7338eb5

Please sign in to comment.