-
Notifications
You must be signed in to change notification settings - Fork 242
Expand file tree
/
Copy pathAggregate.cpp
More file actions
361 lines (333 loc) · 15.5 KB
/
Copy pathAggregate.cpp
File metadata and controls
361 lines (333 loc) · 15.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
/*
* Souffle - A Datalog Compiler
* Copyright (c) 2020, The Souffle Developers. All rights reserved.
* Licensed under the Universal Permissive License v 1.0 as shown at:
* - https://opensource.org/licenses/UPL
* - <souffle root>/licenses/SOUFFLE-UPL.txt
*/
/************************************************************************
*
* @file Ground.cpp
*
* Implements Aggregate Analysis methods to determine scope of variables
*
***********************************************************************/
#include "ast/analysis/Aggregate.h"
#include "ast/Aggregator.h"
#include "ast/Atom.h"
#include "ast/Clause.h"
#include "ast/Relation.h"
#include "ast/TranslationUnit.h"
#include "ast/Variable.h"
#include "ast/analysis/Ground.h"
#include "ast/utility/Utils.h"
#include "ast/utility/Visitor.h"
#include "souffle/utility/StringUtil.h"
#include <algorithm>
#include <map>
#include <memory>
#include <ostream>
#include <set>
#include <string>
#include <utility>
#include <vector>
namespace souffle::ast::analysis {
/**
* Computes the set of local variables
* in an aggregate expression.
* This is just the set of all variables occurring in the aggregate
* MINUS the injected variables
* MINUS the witness variables. :)
*
**/
std::set<std::string> getLocalVariables(
const TranslationUnit& tu, const Clause& clause, const Aggregator& aggregate) {
std::set<std::string> allVariablesInAggregate;
visit(aggregate, [&](const Variable& v) { allVariablesInAggregate.insert(v.getName()); });
std::set<std::string> injectedVariables = getInjectedVariables(tu, clause, aggregate);
std::set<std::string> witnessVariables = getWitnessVariables(tu, clause, aggregate);
std::set<std::string> localVariables;
for (const std::string& name : allVariablesInAggregate) {
if (injectedVariables.find(name) == injectedVariables.end() &&
witnessVariables.find(name) == witnessVariables.end()) {
localVariables.insert(name);
}
}
return localVariables;
}
/**
* Computes the set of witness variables that are used in the aggregate
* A variable is a witness if it occurs in the aggregate body (but not in an inner aggregate)
* and also occurs ungrounded in the outer scope.
**/
std::set<std::string> getWitnessVariables(
const TranslationUnit& tu, const Clause& clause, const Aggregator& aggregate) {
// 1. Create an aggregatorless clause so that we can analyse the groundings
// in the rule scope. This is one where we replace every aggregator with
// a variable, and then provide a grounding for that variable.
struct M : public NodeMapper {
// Variables introduced to replace aggregators
mutable std::set<std::string> aggregatorVariables;
const std::set<std::string>& getAggregatorVariables() {
return aggregatorVariables;
}
std::unique_ptr<Node> operator()(std::unique_ptr<Node> node) const override {
static int numReplaced = 0;
if (isA<Aggregator>(node)) {
// Replace the aggregator with a variable
std::stringstream newVariableName;
newVariableName << "+aggr_var_" << numReplaced++;
// Keep track of which variables are bound to aggregators
aggregatorVariables.insert(newVariableName.str());
return mk<Variable>(newVariableName.str());
}
node->apply(*this);
return node;
}
};
auto aggregatorlessClause = mk<Clause>(QualifiedName::fromString("*"));
aggregatorlessClause->setBodyLiterals(clone(clause.getBodyLiterals()));
auto negatedHead = mk<Negation>(clone(clause.getHead()));
aggregatorlessClause->addToBody(std::move(negatedHead));
// Replace all aggregates with variables
M update;
aggregatorlessClause->apply(update);
auto groundingAtom = mk<Atom>(QualifiedName::fromString("+grounding_atom"));
for (const std::string& variableName : update.getAggregatorVariables()) {
groundingAtom->addArgument(mk<Variable>(variableName));
}
aggregatorlessClause->addToBody(std::move(groundingAtom));
// 2. Create an aggregate clause so that we can check
// that it IS this aggregate giving a grounding to the candidate variable.
auto aggregateSubclause = mk<Clause>(QualifiedName::fromString("*"));
aggregateSubclause->setBodyLiterals(clone(aggregate.getBodyLiterals()));
std::set<std::string> witnessVariables;
auto isGroundedInAggregateSubclause = analysis::getGroundedTerms(tu, *aggregateSubclause);
// 3. Calculate all the witness variables
// A witness will occur ungrounded in the aggregatorlessClause
for (const auto& argPair : analysis::getGroundedTerms(tu, *aggregatorlessClause)) {
if (const auto* variable = as<Variable>(argPair.first)) {
bool variableIsGrounded = argPair.second;
if (!variableIsGrounded) {
// then we expect it to be grounded in the aggregate subclause
// if it's a witness!!
for (const auto& aggArgPair : isGroundedInAggregateSubclause) {
if (const auto* var = as<Variable>(aggArgPair.first)) {
bool aggVariableIsGrounded = aggArgPair.second;
if (var->getName() == variable->getName() && aggVariableIsGrounded) {
witnessVariables.insert(variable->getName());
}
}
}
}
}
}
// 4. A witness variable may actually "originate" from an outer scope and
// just have been injected into this inner aggregate. Just check the set of injected variables
// and quickly minus them out.
std::set<std::string> injectedVariables = analysis::getInjectedVariables(tu, clause, aggregate);
for (const std::string& injected : injectedVariables) {
witnessVariables.erase(injected);
}
return witnessVariables;
}
/**
* Computes the set of variables occurring outside the aggregate
**/
std::set<std::string> getVariablesOutsideAggregate(const Clause& clause, const Aggregator& aggregate) {
std::map<std::string, int> variableOccurrences;
visit(clause, [&](const Variable& var) { variableOccurrences[var.getName()]++; });
visit(aggregate, [&](const Variable& var) { variableOccurrences[var.getName()]--; });
std::set<std::string> variablesOutsideAggregate;
for (auto const& pair : variableOccurrences) {
std::string v = pair.first;
int numOccurrences = pair.second;
if (numOccurrences > 0) {
variablesOutsideAggregate.insert(v);
}
}
return variablesOutsideAggregate;
}
std::string findUniqueVariableName(const Clause& clause, const std::string& base) {
std::set<std::string> variablesInClause;
visit(clause, [&](const Variable& v) { variablesInClause.insert(v.getName()); });
int varNum = 0;
std::string candidate = base;
while (variablesInClause.find(candidate) != variablesInClause.end()) {
candidate = base + toString(varNum++);
}
return candidate;
}
std::string findUniqueRelationName(const Program& program, const std::string& base) {
int counter = 0;
auto candidate = base;
while (program.getRelation(QualifiedName::fromString(candidate)) != nullptr) {
candidate = base + toString(counter++);
}
return candidate;
}
/**
* Given an aggregate and a clause, we find all the variables that have been
* injected into the aggregate.
* This means that the variable occurs grounded in an outer scope.
* BUT does not occur in the target expression.
**/
std::set<std::string> getInjectedVariables(
const TranslationUnit& tu, const Clause& clause, const Aggregator& aggregate) {
/**
* Imagine we have this:
*
* A(letter, x) :-
* a(letter, _),
* x = min y : { a(l, y),
* y < max y : { a(letter, y) } },
* z = max y : {B(y) }.
*
* We need to figure out the base literal in which this aggregate occurs,
* so that we don't delete them
* Other aggregates occurring in the rule are irrelevant, so we'll replace
* the aggregate with a dummy aggregate variable. Then after that,
*
* 2. A(letter, x) :-
* a(letter, _),
* x = min y : { a(l, y),
* y < max y : { a(letter, y) } },
* z = aggr_var_0.
*
* We should also ground all the aggr_vars so that this grounding
* transfers to whatever variable they might be assigned to.
*
* A(letter, x) :-
* a(letter, _),
* x = min y : { a(l, y),
* y < max y : { a(letter, y) } },
* z = aggr_var_0,
* grounding_atom(aggr_var_0).
*
* Remember to negate the head atom and add it to the body. The clause we deal with will have a
* dummy head (*())
*
* *() :- !A(letter, x), a(letter, _), x = min y : { a(l, y), y < max y : { a(letter, y) } },
* z = aggr_var_0, grounding_atom(aggr_var_0).
*
* Replace the original aggregate with the same shit because we can't be counting local variables n shit
*
* *() :- !A(letter, x), a(letter, _), x = min y : { a(l, y), y < aggr_var_1 } }, z = aggr_var_0,
* grounding_atom(aggr_var_0).
*
* Now, as long as we recorded all of the variables that occurred inside the aggregate, the intersection
* of these two sets (variables in an outer scope) and (variables occurring in t
**/
/**
* A variable is considered *injected* into an aggregate if it occurs both within the aggregate AS WELL
* AS *grounded* in the outer scope. An outer scope could be an outer aggregate, or also just the
*base/rule scope. (i.e. not inside of another aggregate that is not an ancestor of this one).
*
* So then in order to calculate the set of variables that have been injected into an aggregate, we
* perform the following steps:
*
* 1. Find the set of variables occurring within the aggregate "aggregate"
* 2. Find the set of variables that occur grounded in the outside scope
* 2a. Replace all non-ancestral aggregates with variables so that we can ignore their variable
* contents (it is not relevant). 2b. Replace the target aggregate with a variable as well 2c. Ground all
* occurrences of the non-ancestral aggregate variable replacements (i.e. grounding_atom(+aggr_var_0)) 2d.
* Visit all variables occurring in this tweaked clause that we made from steps 2a-2c, and check if they
* are grounded, and also occur in the set of target aggregate variables. Then it is an injected variable.
**/
// Step 1
std::set<std::string> variablesInTargetAggregate;
visit(aggregate,
[&](const Variable& variable) { variablesInTargetAggregate.insert(variable.getName()); });
std::set<Own<Aggregator>> ancestorAggregates;
visit(clause, [&](const Aggregator& ancestor) {
visit(ancestor, [&](const Aggregator& agg) {
if (agg == aggregate) {
ancestorAggregates.insert(clone(ancestor));
}
});
});
// 1. Replace non-ancestral aggregates with variables
struct ReplaceAggregatesWithVariables : public NodeMapper {
// Variables introduced to replace aggregators
mutable std::set<std::string> aggregatorVariables;
// ancestors are never replaced so don't need to clone, but actually
// it will have a child that will become invalid at one point. This is a concern. Need to clone these
// bastards too.
std::set<Own<Aggregator>> ancestors;
// make sure you clone the agg first.
Own<Aggregator> targetAggregate;
const std::set<std::string>& getAggregatorVariables() {
return aggregatorVariables;
}
ReplaceAggregatesWithVariables(std::set<Own<Aggregator>> ancestors, Own<Aggregator> targetAggregate)
: ancestors(std::move(ancestors)), targetAggregate(std::move(targetAggregate)) {}
std::unique_ptr<Node> operator()(std::unique_ptr<Node> node) const override {
static int numReplaced = 0;
if (auto* aggregate = as<Aggregator>(node)) {
// If we come across an aggregate that is NOT an ancestor of
// the target aggregate, or that IS itself the target aggregate,
// we should replace it with a dummy variable.
bool isAncestor = false;
for (auto& ancestor : ancestors) {
if (*ancestor == *aggregate) {
isAncestor = true;
break;
}
}
if (!isAncestor || *aggregate == *targetAggregate) {
// Replace the aggregator with a variable
std::stringstream newVariableName;
newVariableName << "+aggr_var_" << numReplaced++;
// Keep track of which variables are bound to aggregators
// (only applicable to non-ancestral aggregates)
if (!isAncestor) {
// we don't want to ground the target aggregate
// (why?) because these variables could be injected
// and that is fine.
aggregatorVariables.insert(newVariableName.str());
// but we are not supposed to be judging the aggregate as grounded and injected into
// the aggregate, that wouldn't make sense
}
return mk<Variable>(newVariableName.str());
}
}
node->apply(*this);
return node;
}
};
// 2. make a clone of the clause and then apply that mapper onto it
auto clauseCopy = clone(clause);
auto tweakedClause = mk<Clause>(QualifiedName::fromString("*"));
tweakedClause->setBodyLiterals(clone(clause.getBodyLiterals()));
// copy in the head as a negated atom
tweakedClause->addToBody(mk<Negation>(clone(clause.getHead())));
// copy in body literals and also add the old head as a negated atom
ReplaceAggregatesWithVariables update(std::move(ancestorAggregates), clone(aggregate));
tweakedClause->apply(update);
// the update will now tell us which variables we need to ground!
auto groundingAtom = mk<Atom>(QualifiedName::fromString("+grounding_atom"));
for (const std::string& variableName : update.getAggregatorVariables()) {
groundingAtom->addArgument(mk<Variable>(variableName));
}
// add the newly created grounding atom to the body
tweakedClause->addToBody(std::move(groundingAtom));
std::set<std::string> injectedVariables;
// Search through the tweakedClause to find groundings!
for (const auto& argPair : analysis::getGroundedTerms(tu, *tweakedClause)) {
if (const auto* variable = as<Variable>(argPair.first)) {
bool varIsGrounded = argPair.second;
if (varIsGrounded && variablesInTargetAggregate.find(variable->getName()) !=
variablesInTargetAggregate.end()) {
// then we have found an injected variable, lovely!!
injectedVariables.insert(variable->getName());
}
}
}
// Remove any variables that occur in the target expression of the aggregate
if (aggregate.getTargetExpression() != nullptr) {
visit(*aggregate.getTargetExpression(),
[&](const Variable& v) { injectedVariables.erase(v.getName()); });
}
return injectedVariables;
}
} // namespace souffle::ast::analysis