mirror of
https://github.com/llvm/llvm-project.git
synced 2025-05-08 00:46:04 +00:00

The SAT solver imported its constraints by iterating over an unordered DenseSet. The path taken, and ultimately the runtime, the specific solution found, and whether it would time out or complete could depend on the iteration order. Instead, have the caller specify an ordered collection of constraints. If this is built in a deterministic way, the system can have deterministic behavior. (The main alternative is to sort the constraints by value, but this option is simpler today). A lot of nondeterminism still appears to be remain in the framework, so today the solver's inputs themselves are not deterministic yet. Differential Revision: https://reviews.llvm.org/D153584
257 lines
7.8 KiB
C++
257 lines
7.8 KiB
C++
//===- DebugSupport.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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file defines functions which generate more readable forms of data
|
|
// structures used in the dataflow analyses, for debugging purposes.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include <utility>
|
|
|
|
#include "clang/Analysis/FlowSensitive/DebugSupport.h"
|
|
#include "clang/Analysis/FlowSensitive/Solver.h"
|
|
#include "clang/Analysis/FlowSensitive/Value.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/ADT/StringSet.h"
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
#include "llvm/Support/FormatAdapters.h"
|
|
#include "llvm/Support/FormatCommon.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
|
|
namespace clang {
|
|
namespace dataflow {
|
|
|
|
using llvm::AlignStyle;
|
|
using llvm::fmt_pad;
|
|
using llvm::formatv;
|
|
|
|
llvm::StringRef debugString(Value::Kind Kind) {
|
|
switch (Kind) {
|
|
case Value::Kind::Integer:
|
|
return "Integer";
|
|
case Value::Kind::Reference:
|
|
return "Reference";
|
|
case Value::Kind::Pointer:
|
|
return "Pointer";
|
|
case Value::Kind::Struct:
|
|
return "Struct";
|
|
case Value::Kind::AtomicBool:
|
|
return "AtomicBool";
|
|
case Value::Kind::TopBool:
|
|
return "TopBool";
|
|
case Value::Kind::Conjunction:
|
|
return "Conjunction";
|
|
case Value::Kind::Disjunction:
|
|
return "Disjunction";
|
|
case Value::Kind::Negation:
|
|
return "Negation";
|
|
case Value::Kind::Implication:
|
|
return "Implication";
|
|
case Value::Kind::Biconditional:
|
|
return "Biconditional";
|
|
}
|
|
llvm_unreachable("Unhandled value kind");
|
|
}
|
|
|
|
llvm::StringRef debugString(Solver::Result::Assignment Assignment) {
|
|
switch (Assignment) {
|
|
case Solver::Result::Assignment::AssignedFalse:
|
|
return "False";
|
|
case Solver::Result::Assignment::AssignedTrue:
|
|
return "True";
|
|
}
|
|
llvm_unreachable("Booleans can only be assigned true/false");
|
|
}
|
|
|
|
llvm::StringRef debugString(Solver::Result::Status Status) {
|
|
switch (Status) {
|
|
case Solver::Result::Status::Satisfiable:
|
|
return "Satisfiable";
|
|
case Solver::Result::Status::Unsatisfiable:
|
|
return "Unsatisfiable";
|
|
case Solver::Result::Status::TimedOut:
|
|
return "TimedOut";
|
|
}
|
|
llvm_unreachable("Unhandled SAT check result status");
|
|
}
|
|
|
|
namespace {
|
|
|
|
class DebugStringGenerator {
|
|
public:
|
|
explicit DebugStringGenerator(
|
|
llvm::DenseMap<const AtomicBoolValue *, std::string> AtomNamesArg)
|
|
: Counter(0), AtomNames(std::move(AtomNamesArg)) {
|
|
#ifndef NDEBUG
|
|
llvm::StringSet<> Names;
|
|
for (auto &N : AtomNames) {
|
|
assert(Names.insert(N.second).second &&
|
|
"The same name must not assigned to different atoms");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// Returns a string representation of a boolean value `B`.
|
|
std::string debugString(const BoolValue &B, size_t Depth = 0) {
|
|
std::string S;
|
|
switch (B.getKind()) {
|
|
case Value::Kind::AtomicBool: {
|
|
S = getAtomName(&cast<AtomicBoolValue>(B));
|
|
break;
|
|
}
|
|
case Value::Kind::TopBool: {
|
|
S = "top";
|
|
break;
|
|
}
|
|
case Value::Kind::Conjunction: {
|
|
auto &C = cast<ConjunctionValue>(B);
|
|
auto L = debugString(C.getLeftSubValue(), Depth + 1);
|
|
auto R = debugString(C.getRightSubValue(), Depth + 1);
|
|
S = formatv("(and\n{0}\n{1})", L, R);
|
|
break;
|
|
}
|
|
case Value::Kind::Disjunction: {
|
|
auto &D = cast<DisjunctionValue>(B);
|
|
auto L = debugString(D.getLeftSubValue(), Depth + 1);
|
|
auto R = debugString(D.getRightSubValue(), Depth + 1);
|
|
S = formatv("(or\n{0}\n{1})", L, R);
|
|
break;
|
|
}
|
|
case Value::Kind::Negation: {
|
|
auto &N = cast<NegationValue>(B);
|
|
S = formatv("(not\n{0})", debugString(N.getSubVal(), Depth + 1));
|
|
break;
|
|
}
|
|
case Value::Kind::Implication: {
|
|
auto &IV = cast<ImplicationValue>(B);
|
|
auto L = debugString(IV.getLeftSubValue(), Depth + 1);
|
|
auto R = debugString(IV.getRightSubValue(), Depth + 1);
|
|
S = formatv("(=>\n{0}\n{1})", L, R);
|
|
break;
|
|
}
|
|
case Value::Kind::Biconditional: {
|
|
auto &BV = cast<BiconditionalValue>(B);
|
|
auto L = debugString(BV.getLeftSubValue(), Depth + 1);
|
|
auto R = debugString(BV.getRightSubValue(), Depth + 1);
|
|
S = formatv("(=\n{0}\n{1})", L, R);
|
|
break;
|
|
}
|
|
default:
|
|
llvm_unreachable("Unhandled value kind");
|
|
}
|
|
auto Indent = Depth * 4;
|
|
return formatv("{0}", fmt_pad(S, Indent, 0));
|
|
}
|
|
|
|
std::string debugString(const llvm::ArrayRef<BoolValue *> &Constraints) {
|
|
std::string Result;
|
|
for (const BoolValue *S : Constraints) {
|
|
Result += debugString(*S);
|
|
Result += '\n';
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
/// Returns a string representation of a set of boolean `Constraints` and the
|
|
/// `Result` of satisfiability checking on the `Constraints`.
|
|
std::string debugString(ArrayRef<BoolValue *> &Constraints,
|
|
const Solver::Result &Result) {
|
|
auto Template = R"(
|
|
Constraints
|
|
------------
|
|
{0:$[
|
|
|
|
]}
|
|
------------
|
|
{1}.
|
|
{2}
|
|
)";
|
|
|
|
std::vector<std::string> ConstraintsStrings;
|
|
ConstraintsStrings.reserve(Constraints.size());
|
|
for (auto &Constraint : Constraints) {
|
|
ConstraintsStrings.push_back(debugString(*Constraint));
|
|
}
|
|
|
|
auto StatusString = clang::dataflow::debugString(Result.getStatus());
|
|
auto Solution = Result.getSolution();
|
|
auto SolutionString = Solution ? "\n" + debugString(*Solution) : "";
|
|
|
|
return formatv(
|
|
Template,
|
|
llvm::make_range(ConstraintsStrings.begin(), ConstraintsStrings.end()),
|
|
StatusString, SolutionString);
|
|
}
|
|
|
|
private:
|
|
/// Returns a string representation of a truth assignment to atom booleans.
|
|
std::string debugString(
|
|
const llvm::DenseMap<AtomicBoolValue *, Solver::Result::Assignment>
|
|
&AtomAssignments) {
|
|
size_t MaxNameLength = 0;
|
|
for (auto &AtomName : AtomNames) {
|
|
MaxNameLength = std::max(MaxNameLength, AtomName.second.size());
|
|
}
|
|
|
|
std::vector<std::string> Lines;
|
|
for (auto &AtomAssignment : AtomAssignments) {
|
|
auto Line = formatv("{0} = {1}",
|
|
fmt_align(getAtomName(AtomAssignment.first),
|
|
AlignStyle::Left, MaxNameLength),
|
|
clang::dataflow::debugString(AtomAssignment.second));
|
|
Lines.push_back(Line);
|
|
}
|
|
llvm::sort(Lines);
|
|
|
|
return formatv("{0:$[\n]}", llvm::make_range(Lines.begin(), Lines.end()));
|
|
}
|
|
|
|
/// Returns the name assigned to `Atom`, either user-specified or created by
|
|
/// default rules (B0, B1, ...).
|
|
std::string getAtomName(const AtomicBoolValue *Atom) {
|
|
auto Entry = AtomNames.try_emplace(Atom, formatv("B{0}", Counter));
|
|
if (Entry.second) {
|
|
Counter++;
|
|
}
|
|
return Entry.first->second;
|
|
}
|
|
|
|
// Keep track of number of atoms without a user-specified name, used to assign
|
|
// non-repeating default names to such atoms.
|
|
size_t Counter;
|
|
|
|
// Keep track of names assigned to atoms.
|
|
llvm::DenseMap<const AtomicBoolValue *, std::string> AtomNames;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
std::string
|
|
debugString(const BoolValue &B,
|
|
llvm::DenseMap<const AtomicBoolValue *, std::string> AtomNames) {
|
|
return DebugStringGenerator(std::move(AtomNames)).debugString(B);
|
|
}
|
|
|
|
std::string
|
|
debugString(llvm::ArrayRef<BoolValue *> Constraints,
|
|
llvm::DenseMap<const AtomicBoolValue *, std::string> AtomNames) {
|
|
return DebugStringGenerator(std::move(AtomNames)).debugString(Constraints);
|
|
}
|
|
|
|
std::string
|
|
debugString(ArrayRef<BoolValue *> Constraints, const Solver::Result &Result,
|
|
llvm::DenseMap<const AtomicBoolValue *, std::string> AtomNames) {
|
|
return DebugStringGenerator(std::move(AtomNames))
|
|
.debugString(Constraints, Result);
|
|
}
|
|
|
|
} // namespace dataflow
|
|
} // namespace clang
|