souffle  2.0.2-371-g6315b36
Ground.cpp
Go to the documentation of this file.
1 /*
2  * Souffle - A Datalog Compiler
3  * Copyright (c) 2020, The Souffle Developers. All rights reserved.
4  * Licensed under the Universal Permissive License v 1.0 as shown at:
5  * - https://opensource.org/licenses/UPL
6  * - <souffle root>/licenses/SOUFFLE-UPL.txt
7  */
8 
9 /************************************************************************
10  *
11  * @file Ground.cpp
12  *
13  * Implements AST Analysis methods to find the grounded arguments in a clause
14  *
15  ***********************************************************************/
16 
17 #include "ast/analysis/Ground.h"
18 #include "RelationTag.h"
19 #include "ast/Aggregator.h"
20 #include "ast/Atom.h"
21 #include "ast/BinaryConstraint.h"
22 #include "ast/BranchInit.h"
23 #include "ast/Clause.h"
24 #include "ast/Constant.h"
25 #include "ast/Functor.h"
26 #include "ast/Negation.h"
27 #include "ast/RecordInit.h"
28 #include "ast/Relation.h"
29 #include "ast/TranslationUnit.h"
30 #include "ast/TypeCast.h"
36 #include <algorithm>
37 #include <map>
38 #include <memory>
39 #include <ostream>
40 #include <set>
41 #include <utility>
42 #include <vector>
43 
44 namespace souffle::ast::analysis {
45 
46 namespace {
47 
48 // -----------------------------------------------------------------------------
49 // Boolean Disjunct Lattice
50 // -----------------------------------------------------------------------------
51 
52 /**
53  * The disjunct meet operator, aka boolean or.
54  */
55 struct bool_or {
56  bool operator()(bool& a, bool b) const {
57  bool t = a;
58  a = a || b;
59  return t != a;
60  }
61 };
62 
63 /**
64  * A factory producing the value false.
65  */
66 struct false_factory {
67  bool operator()() const {
68  return false;
69  }
70 };
71 
72 /**
73  * The definition of a lattice utilizing the boolean values {true} and {false} as
74  * its value set and the || operation as its meet operation. Correspondingly,
75  * the bottom value is {false} and the top value {true}.
76  */
77 struct bool_disjunct_lattic : public property_space<bool, bool_or, false_factory> {};
78 
79 /** A base type for analysis based on the boolean disjunct lattice */
80 using BoolDisjunctVar = ConstraintAnalysisVar<bool_disjunct_lattic>;
81 
82 /** A base type for constraints on the boolean disjunct lattice */
83 using BoolDisjunctConstraint = std::shared_ptr<Constraint<BoolDisjunctVar>>;
84 
85 /**
86  * A constraint factory for a constraint ensuring that the value assigned to the
87  * given variable is (at least) {true}
88  */
89 BoolDisjunctConstraint isTrue(const BoolDisjunctVar& var) {
90  struct C : public Constraint<BoolDisjunctVar> {
91  BoolDisjunctVar var;
92  C(BoolDisjunctVar var) : var(std::move(var)) {}
93  bool update(Assignment<BoolDisjunctVar>& ass) const override {
94  auto res = !ass[var];
95  ass[var] = true;
96  return res;
97  }
98  void print(std::ostream& out) const override {
99  out << var << " is true";
100  }
101  };
102  return std::make_shared<C>(var);
103 }
104 
105 /**
106  * A constraint factory for a constraint ensuring the constraint
107  *
108  * a ⇒ b
109  *
110  * Hence, whenever a is mapped to {true}, so is b.
111  */
112 BoolDisjunctConstraint imply(const BoolDisjunctVar& a, const BoolDisjunctVar& b) {
113  return sub(a, b, "⇒");
114 }
115 
116 /**
117  * A constraint factory for a constraint ensuring the constraint
118  *
119  * vars[0] ∧ vars[1] ∧ ... ∧ vars[n] ⇒ res
120  *
121  * Hence, whenever all variables vars[i] are mapped to true, so is res.
122  */
123 BoolDisjunctConstraint imply(const std::vector<BoolDisjunctVar>& vars, const BoolDisjunctVar& res) {
124  struct C : public Constraint<BoolDisjunctVar> {
125  BoolDisjunctVar res;
126  std::vector<BoolDisjunctVar> vars;
127 
128  C(BoolDisjunctVar res, std::vector<BoolDisjunctVar> vars)
129  : res(std::move(res)), vars(std::move(vars)) {}
130 
131  bool update(Assignment<BoolDisjunctVar>& ass) const override {
132  bool r = ass[res];
133  if (r) {
134  return false;
135  }
136 
137  for (const auto& cur : vars) {
138  if (!ass[cur]) {
139  return false;
140  }
141  }
142 
143  ass[res] = true;
144  return true;
145  }
146 
147  void print(std::ostream& out) const override {
148  out << join(vars, " ∧ ") << " ⇒ " << res;
149  }
150  };
151 
152  return std::make_shared<C>(res, vars);
153 }
154 
155 struct GroundednessAnalysis : public ConstraintAnalysis<BoolDisjunctVar> {
156  const RelationDetailCacheAnalysis& relCache;
157  std::set<const Atom*> ignore;
158 
159  GroundednessAnalysis(const TranslationUnit& tu)
160  : relCache(*tu.getAnalysis<RelationDetailCacheAnalysis>()) {}
161 
162  // atoms are producing grounded variables
163  void visitAtom(const Atom& cur) override {
164  // some atoms need to be skipped (head or negation)
165  if (ignore.find(&cur) != ignore.end()) {
166  return;
167  }
168 
169  // all arguments are grounded
170  for (const auto& arg : cur.getArguments()) {
171  addConstraint(isTrue(getVar(arg)));
172  }
173  }
174 
175  // negations need to be skipped
176  void visitNegation(const Negation& cur) override {
177  // add nested atom to black-list
178  ignore.insert(cur.getAtom());
179  }
180 
181  // also skip head if we don't have an inline qualifier
182  void visitClause(const Clause& clause) override {
183  if (auto clauseHead = clause.getHead()) {
184  auto relation = relCache.getRelation(clauseHead->getQualifiedName());
185  // Only skip the head if the relation ISN'T inline. Keeping the head will ground
186  // any mentioned variables, allowing us to pretend they're grounded.
187  if (!(relation && relation->hasQualifier(RelationQualifier::INLINE))) {
188  ignore.insert(clauseHead);
189  }
190  }
191  }
192 
193  // binary equality relations propagates groundness
194  void visitBinaryConstraint(const BinaryConstraint& cur) override {
195  // only target equality
196  if (!isEqConstraint(cur.getBaseOperator())) {
197  return;
198  }
199 
200  // if equal, link right and left side
201  auto lhs = getVar(cur.getLHS());
202  auto rhs = getVar(cur.getRHS());
203 
204  addConstraint(imply(lhs, rhs));
205  addConstraint(imply(rhs, lhs));
206  }
207 
208  // record init nodes
209  void visitRecordInit(const RecordInit& init) override {
210  auto cur = getVar(init);
211 
212  std::vector<BoolDisjunctVar> vars;
213 
214  // if record is grounded, so are all its arguments
215  for (const auto& arg : init.getArguments()) {
216  auto arg_var = getVar(arg);
217  addConstraint(imply(cur, arg_var));
218  vars.push_back(arg_var);
219  }
220 
221  // if all arguments are grounded, so is the record
222  addConstraint(imply(vars, cur));
223  }
224 
225  void visitBranchInit(const BranchInit& adt) override {
226  auto branchVar = getVar(adt);
227 
228  std::vector<BoolDisjunctVar> argVars;
229 
230  // If the branch is grounded so are its arguments.
231  for (const auto* arg : adt.getArguments()) {
232  auto argVar = getVar(arg);
233  addConstraint(imply(branchVar, argVar));
234  argVars.push_back(argVar);
235  }
236 
237  // if all arguments are grounded so is the branch.
238  addConstraint(imply(argVars, branchVar));
239  }
240 
241  // Constants are also sources of grounded values
242  void visitConstant(const Constant& constant) override {
243  addConstraint(isTrue(getVar(constant)));
244  }
245 
246  // Aggregators are grounding values
247  void visitAggregator(const Aggregator& aggregator) override {
248  addConstraint(isTrue(getVar(aggregator)));
249  }
250 
251  // Functors with grounded values are grounded values
252  void visitFunctor(const Functor& functor) override {
253  auto var = getVar(functor);
254  std::vector<BoolDisjunctVar> varArgs;
255  for (const auto& arg : functor.getArguments()) {
256  varArgs.push_back(getVar(arg));
257  }
258  addConstraint(imply(varArgs, var));
259  }
260 
261  // casts propogate groundedness in and out
262  void visitTypeCast(const ast::TypeCast& cast) override {
263  addConstraint(imply(getVar(cast.getValue()), getVar(cast)));
264  }
265 };
266 
267 } // namespace
268 
269 /***
270  * computes for variables in the clause whether they are grounded
271  */
272 std::map<const Argument*, bool> getGroundedTerms(const TranslationUnit& tu, const Clause& clause) {
273  // run analysis on given clause
274  return GroundednessAnalysis(tu).analyse(clause);
275 }
276 
277 } // namespace souffle::ast::analysis
BinaryConstraintOps.h
TranslationUnit.h
Functor.h
ignore
std::set< const Atom * > ignore
Definition: Ground.cpp:169
souffle::ast::analysis::sub
std::shared_ptr< Constraint< Var > > sub(const Var &a, const Var &b, const std::string &symbol="⊑")
A generic factory for constraints of the form.
Definition: ConstraintSystem.h:228
RelationDetailCache.h
relation
Relation & relation
Definition: Reader.h:130
Constraint.h
Relation.h
rhs
Own< Argument > rhs
Definition: ResolveAliases.cpp:185
souffle::ast::analysis::getGroundedTerms
std::map< const Argument *, bool > getGroundedTerms(const TranslationUnit &tu, const Clause &clause)
Analyse the given clause and computes for each contained argument whether it is a grounded value or n...
Definition: Ground.cpp:278
Constant.h
ConstraintSystem.h
lhs
Own< Argument > lhs
Definition: ResolveAliases.cpp:184
TypeCast.h
Ground.h
RelationTag.h
souffle::join
detail::joined_sequence< Iter, Printer > join(const Iter &a, const Iter &b, const std::string &sep, const Printer &p)
Creates an object to be forwarded to some output stream for printing sequences of elements interspers...
Definition: StreamUtil.h:175
Atom.h
relCache
const RelationDetailCacheAnalysis & relCache
Definition: Ground.cpp:168
Negation.h
BranchInit.h
souffle::ram::isTrue
bool isTrue(const Condition *cond)
Determines if a condition represents true.
Definition: Utils.h:45
souffle::isEqConstraint
bool isEqConstraint(const BinaryConstraintOp constraintOp)
Definition: BinaryConstraintOps.h:124
Aggregator.h
b
l j a showGridBackground &&c b raw series this eventEmitter b
Definition: htmlJsChartistMin.h:15
std
Definition: Brie.h:3053
souffle::RelationQualifier::INLINE
@ INLINE
StreamUtil.h
Clause.h
souffle::ast::analysis
Definition: Aggregate.cpp:39
BinaryConstraint.h
RecordInit.h