//===- CNFFormula.cpp -------------------------------------------*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// // // A representation of a boolean formula in 3-CNF. // //===----------------------------------------------------------------------===// #include "clang/Analysis/FlowSensitive/CNFFormula.h" #include "llvm/ADT/DenseSet.h" #include namespace clang { namespace dataflow { namespace { /// Applies simplifications while building up a BooleanFormula. /// We keep track of unit clauses, which tell us variables that must be /// true/false in any model that satisfies the overall formula. /// Such variables can be dropped from subsequently-added clauses, which /// may in turn yield more unit clauses or even a contradiction. /// The total added complexity of this preprocessing is O(N) where we /// for every clause, we do a lookup for each unit clauses. /// The lookup is O(1) on average. This method won't catch all /// contradictory formulas, more passes can in principle catch /// more cases but we leave all these and the general case to the /// proper SAT solver. struct CNFFormulaBuilder { // Formula should outlive CNFFormulaBuilder. explicit CNFFormulaBuilder(CNFFormula &CNF) : Formula(CNF) {} /// Adds the `L1 v ... v Ln` clause to the formula. Applies /// simplifications, based on single-literal clauses. /// /// Requirements: /// /// `Li` must not be `NullLit`. /// /// All literals must be distinct. void addClause(ArrayRef Literals) { // We generate clauses with up to 3 literals in this file. assert(!Literals.empty() && Literals.size() <= 3); // Contains literals of the simplified clause. llvm::SmallVector Simplified; for (auto L : Literals) { assert(L != NullLit && llvm::all_of(Simplified, [L](Literal S) { return S != L; })); auto X = var(L); if (trueVars.contains(X)) { // X must be true if (isPosLit(L)) return; // Omit clause `(... v X v ...)`, it is `true`. else continue; // Omit `!X` from `(... v !X v ...)`. } if (falseVars.contains(X)) { // X must be false if (isNegLit(L)) return; // Omit clause `(... v !X v ...)`, it is `true`. else continue; // Omit `X` from `(... v X v ...)`. } Simplified.push_back(L); } if (Simplified.empty()) { // Simplification made the clause empty, which is equivalent to `false`. // We already know that this formula is unsatisfiable. Formula.addClause(Simplified); return; } if (Simplified.size() == 1) { // We have new unit clause. const Literal lit = Simplified.front(); const Variable v = var(lit); if (isPosLit(lit)) trueVars.insert(v); else falseVars.insert(v); } Formula.addClause(Simplified); } /// Returns true if we observed a contradiction while adding clauses. /// In this case then the formula is already known to be unsatisfiable. bool isKnownContradictory() { return Formula.knownContradictory(); } private: CNFFormula &Formula; llvm::DenseSet trueVars; llvm::DenseSet falseVars; }; } // namespace CNFFormula::CNFFormula(Variable LargestVar) : LargestVar(LargestVar), KnownContradictory(false) { Clauses.push_back(0); ClauseStarts.push_back(0); } void CNFFormula::addClause(ArrayRef lits) { assert(llvm::all_of(lits, [](Literal L) { return L != NullLit; })); if (lits.empty()) KnownContradictory = true; const size_t S = Clauses.size(); ClauseStarts.push_back(S); Clauses.insert(Clauses.end(), lits.begin(), lits.end()); } CNFFormula buildCNF(const llvm::ArrayRef &Formulas, llvm::DenseMap &Atomics) { // The general strategy of the algorithm implemented below is to map each // of the sub-values in `Vals` to a unique variable and use these variables in // the resulting CNF expression to avoid exponential blow up. The number of // literals in the resulting formula is guaranteed to be linear in the number // of sub-formulas in `Vals`. // Map each sub-formula in `Vals` to a unique variable. llvm::DenseMap FormulaToVar; // Store variable identifiers and Atom of atomic booleans. Variable NextVar = 1; { std::queue UnprocessedFormulas; for (const Formula *F : Formulas) UnprocessedFormulas.push(F); while (!UnprocessedFormulas.empty()) { Variable Var = NextVar; const Formula *F = UnprocessedFormulas.front(); UnprocessedFormulas.pop(); if (!FormulaToVar.try_emplace(F, Var).second) continue; ++NextVar; for (const Formula *Op : F->operands()) UnprocessedFormulas.push(Op); if (F->kind() == Formula::AtomRef) Atomics[Var] = F->getAtom(); } } auto GetVar = [&FormulaToVar](const Formula *F) { auto ValIt = FormulaToVar.find(F); assert(ValIt != FormulaToVar.end()); return ValIt->second; }; CNFFormula CNF(NextVar - 1); std::vector ProcessedSubVals(NextVar, false); CNFFormulaBuilder builder(CNF); // Add a conjunct for each variable that represents a top-level conjunction // value in `Vals`. for (const Formula *F : Formulas) builder.addClause(posLit(GetVar(F))); // Add conjuncts that represent the mapping between newly-created variables // and their corresponding sub-formulas. std::queue UnprocessedFormulas; for (const Formula *F : Formulas) UnprocessedFormulas.push(F); while (!UnprocessedFormulas.empty()) { const Formula *F = UnprocessedFormulas.front(); UnprocessedFormulas.pop(); const Variable Var = GetVar(F); if (ProcessedSubVals[Var]) continue; ProcessedSubVals[Var] = true; switch (F->kind()) { case Formula::AtomRef: break; case Formula::Literal: CNF.addClause(F->literal() ? posLit(Var) : negLit(Var)); break; case Formula::And: { const Variable LHS = GetVar(F->operands()[0]); const Variable RHS = GetVar(F->operands()[1]); if (LHS == RHS) { // `X <=> (A ^ A)` is equivalent to `(!X v A) ^ (X v !A)` which is // already in conjunctive normal form. Below we add each of the // conjuncts of the latter expression to the result. builder.addClause({negLit(Var), posLit(LHS)}); builder.addClause({posLit(Var), negLit(LHS)}); } else { // `X <=> (A ^ B)` is equivalent to `(!X v A) ^ (!X v B) ^ (X v !A v // !B)` which is already in conjunctive normal form. Below we add each // of the conjuncts of the latter expression to the result. builder.addClause({negLit(Var), posLit(LHS)}); builder.addClause({negLit(Var), posLit(RHS)}); builder.addClause({posLit(Var), negLit(LHS), negLit(RHS)}); } break; } case Formula::Or: { const Variable LHS = GetVar(F->operands()[0]); const Variable RHS = GetVar(F->operands()[1]); if (LHS == RHS) { // `X <=> (A v A)` is equivalent to `(!X v A) ^ (X v !A)` which is // already in conjunctive normal form. Below we add each of the // conjuncts of the latter expression to the result. builder.addClause({negLit(Var), posLit(LHS)}); builder.addClause({posLit(Var), negLit(LHS)}); } else { // `X <=> (A v B)` is equivalent to `(!X v A v B) ^ (X v !A) ^ (X v // !B)` which is already in conjunctive normal form. Below we add each // of the conjuncts of the latter expression to the result. builder.addClause({negLit(Var), posLit(LHS), posLit(RHS)}); builder.addClause({posLit(Var), negLit(LHS)}); builder.addClause({posLit(Var), negLit(RHS)}); } break; } case Formula::Not: { const Variable Operand = GetVar(F->operands()[0]); // `X <=> !Y` is equivalent to `(!X v !Y) ^ (X v Y)` which is // already in conjunctive normal form. Below we add each of the // conjuncts of the latter expression to the result. builder.addClause({negLit(Var), negLit(Operand)}); builder.addClause({posLit(Var), posLit(Operand)}); break; } case Formula::Implies: { const Variable LHS = GetVar(F->operands()[0]); const Variable RHS = GetVar(F->operands()[1]); // `X <=> (A => B)` is equivalent to // `(X v A) ^ (X v !B) ^ (!X v !A v B)` which is already in // conjunctive normal form. Below we add each of the conjuncts of // the latter expression to the result. builder.addClause({posLit(Var), posLit(LHS)}); builder.addClause({posLit(Var), negLit(RHS)}); builder.addClause({negLit(Var), negLit(LHS), posLit(RHS)}); break; } case Formula::Equal: { const Variable LHS = GetVar(F->operands()[0]); const Variable RHS = GetVar(F->operands()[1]); if (LHS == RHS) { // `X <=> (A <=> A)` is equivalent to `X` which is already in // conjunctive normal form. Below we add each of the conjuncts of the // latter expression to the result. builder.addClause(posLit(Var)); // No need to visit the sub-values of `Val`. continue; } // `X <=> (A <=> B)` is equivalent to // `(X v A v B) ^ (X v !A v !B) ^ (!X v A v !B) ^ (!X v !A v B)` which // is already in conjunctive normal form. Below we add each of the // conjuncts of the latter expression to the result. builder.addClause({posLit(Var), posLit(LHS), posLit(RHS)}); builder.addClause({posLit(Var), negLit(LHS), negLit(RHS)}); builder.addClause({negLit(Var), posLit(LHS), negLit(RHS)}); builder.addClause({negLit(Var), negLit(LHS), posLit(RHS)}); break; } } if (builder.isKnownContradictory()) { return CNF; } for (const Formula *Child : F->operands()) UnprocessedFormulas.push(Child); } // Unit clauses that were added later were not // considered for the simplification of earlier clauses. Do a final // pass to find more opportunities for simplification. CNFFormula FinalCNF(NextVar - 1); CNFFormulaBuilder FinalBuilder(FinalCNF); // Collect unit clauses. for (ClauseID C = 1; C <= CNF.numClauses(); ++C) { if (CNF.clauseSize(C) == 1) { FinalBuilder.addClause(CNF.clauseLiterals(C)[0]); } } // Add all clauses that were added previously, preserving the order. for (ClauseID C = 1; C <= CNF.numClauses(); ++C) { FinalBuilder.addClause(CNF.clauseLiterals(C)); if (FinalBuilder.isKnownContradictory()) { break; } } // It is possible there were new unit clauses again, but // we stop here and leave the rest to the solver algorithm. return FinalCNF; } } // namespace dataflow } // namespace clang