1 /********************* */
2 /*! \file theory_arrays.h
3 ** \verbatim
4 ** Top contributors (to current version):
5 ** Morgan Deters, Clark Barrett, Andrew Reynolds
6 ** This file is part of the CVC4 project.
7 ** Copyright (c) 2009-2019 by the authors listed in the file AUTHORS
8 ** in the top-level source directory) and their institutional affiliations.
9 ** All rights reserved. See the file COPYING in the top-level source
10 ** directory for licensing information.\endverbatim
11 **
12 ** \brief Theory of arrays
13 **
14 ** Theory of arrays.
15 **/
16
17 #include "cvc4_private.h"
18
19 #ifndef __CVC4__THEORY__ARRAYS__THEORY_ARRAYS_H
20 #define __CVC4__THEORY__ARRAYS__THEORY_ARRAYS_H
21
22 #include <tuple>
23 #include <unordered_map>
24
25 #include "context/cdhashmap.h"
26 #include "context/cdhashset.h"
27 #include "context/cdqueue.h"
28 #include "theory/arrays/array_info.h"
29 #include "theory/arrays/array_proof_reconstruction.h"
30 #include "theory/theory.h"
31 #include "theory/uf/equality_engine.h"
32 #include "util/statistics_registry.h"
33
34 namespace CVC4 {
35 namespace theory {
36 namespace arrays {
37
38 /**
39 * Decision procedure for arrays.
40 *
41 * Overview of decision procedure:
42 *
43 * Preliminary notation:
44 * Stores(a) = {t | a ~ t and t = store( _ _ _ )}
45 * InStores(a) = {t | t = store (b _ _) and a ~ b }
46 * Indices(a) = {i | there exists a term b[i] such that a ~ b or store(b i v)}
47 * ~ represents the equivalence relation based on the asserted equalities in the
48 * current context.
49 *
50 * The rules implemented are the following:
51 * store(b i v)
52 * Row1 -------------------
53 * store(b i v)[i] = v
54 *
55 * store(b i v) a'[j]
56 * Row ---------------------- [ a' ~ store(b i v) or a' ~ b ]
57 * i = j OR a[j] = b[j]
58 *
59 * a b same kind arrays
60 * Ext ------------------------ [ a!= b in current context, k new var]
61 * a = b OR a[k] != b[k]p
62 *
63 *
64 * The Row1 one rule is implemented implicitly as follows:
65 * - for each store(b i v) term add the following equality to the congruence
66 * closure store(b i v)[i] = v
67 * - if one of the literals in a conflict is of the form store(b i v)[i] = v
68 * remove it from the conflict
69 *
70 * Because new store terms are not created, we need to check if we need to
71 * instantiate a new Row axiom in the following cases:
72 * 1. the congruence relation changes (i.e. two terms get merged)
73 * - when a new equality between array terms a = b is asserted we check if
74 * we can instantiate a Row lemma for all pairs of indices i where a is
75 * being read and stores
76 * - this is only done during full effort check
77 * 2. a new read term is created either as a consequences of an Ext lemma or a
78 * Row lemma
79 * - this is implemented in the checkRowForIndex method which is called
80 * when preregistering a term of the form a[i].
81 * - as a consequence lemmas are instantiated even before full effort check
82 *
83 * The Ext axiom is instantiated when a disequality is asserted during full effort
84 * check. Ext lemmas are stored in a cache to prevent instantiating essentially
85 * the same lemma multiple times.
86 */
87
spaces(int level)88 static inline std::string spaces(int level)
89 {
90 std::string indentStr(level, ' ');
91 return indentStr;
92 }
93
94 class TheoryArrays : public Theory {
95
96 /////////////////////////////////////////////////////////////////////////////
97 // MISC
98 /////////////////////////////////////////////////////////////////////////////
99
100 private:
101
102 /** True node for predicates = true */
103 Node d_true;
104
105 /** True node for predicates = false */
106 Node d_false;
107
108 // Statistics
109
110 /** number of Row lemmas */
111 IntStat d_numRow;
112 /** number of Ext lemmas */
113 IntStat d_numExt;
114 /** number of propagations */
115 IntStat d_numProp;
116 /** number of explanations */
117 IntStat d_numExplain;
118 /** calls to non-linear */
119 IntStat d_numNonLinear;
120 /** splits on array variables */
121 IntStat d_numSharedArrayVarSplits;
122 /** splits in getModelVal */
123 IntStat d_numGetModelValSplits;
124 /** conflicts in getModelVal */
125 IntStat d_numGetModelValConflicts;
126 /** splits in setModelVal */
127 IntStat d_numSetModelValSplits;
128 /** conflicts in setModelVal */
129 IntStat d_numSetModelValConflicts;
130
131 // Merge reason types
132
133 /** Merge tag for ROW applications */
134 unsigned d_reasonRow;
135 /** Merge tag for ROW1 applications */
136 unsigned d_reasonRow1;
137 /** Merge tag for EXT applications */
138 unsigned d_reasonExt;
139
140 public:
141
142 TheoryArrays(context::Context* c, context::UserContext* u, OutputChannel& out,
143 Valuation valuation, const LogicInfo& logicInfo,
144 std::string name = "");
145 ~TheoryArrays();
146
147 void setMasterEqualityEngine(eq::EqualityEngine* eq) override;
148
identify()149 std::string identify() const override { return std::string("TheoryArrays"); }
150
151 /////////////////////////////////////////////////////////////////////////////
152 // PREPROCESSING
153 /////////////////////////////////////////////////////////////////////////////
154
155 private:
156
157 // PPNotifyClass: dummy template class for d_ppEqualityEngine - notifications not used
158 class PPNotifyClass {
159 public:
notify(TNode propagation)160 bool notify(TNode propagation) { return true; }
notify(TNode t1,TNode t2)161 void notify(TNode t1, TNode t2) { }
162 };
163
164 /** The notify class for d_ppEqualityEngine */
165 PPNotifyClass d_ppNotify;
166
167 /** Equaltity engine */
168 eq::EqualityEngine d_ppEqualityEngine;
169
170 // List of facts learned by preprocessor - needed for permanent ref for benefit of d_ppEqualityEngine
171 context::CDList<Node> d_ppFacts;
172
173 Node preprocessTerm(TNode term);
174 Node recursivePreprocessTerm(TNode term);
175 bool ppDisequal(TNode a, TNode b);
176 Node solveWrite(TNode term, bool solve1, bool solve2, bool ppCheck);
177
178 public:
179 PPAssertStatus ppAssert(TNode in, SubstitutionMap& outSubstitutions) override;
180 Node ppRewrite(TNode atom) override;
181
182 /////////////////////////////////////////////////////////////////////////////
183 // T-PROPAGATION / REGISTRATION
184 /////////////////////////////////////////////////////////////////////////////
185
186 private:
187 /** Literals to propagate */
188 context::CDList<Node> d_literalsToPropagate;
189
190 /** Index of the next literal to propagate */
191 context::CDO<unsigned> d_literalsToPropagateIndex;
192
193 /** Should be called to propagate the literal. */
194 bool propagate(TNode literal);
195
196 /** Explain why this literal is true by adding assumptions */
197 void explain(TNode literal, std::vector<TNode>& assumptions,
198 eq::EqProof* proof);
199
200 /** For debugging only- checks invariants about when things are preregistered*/
201 context::CDHashSet<Node, NodeHashFunction > d_isPreRegistered;
202
203 /** Helper for preRegisterTerm, also used internally */
204 void preRegisterTermInternal(TNode n);
205
206 public:
207 void preRegisterTerm(TNode n) override;
208 void propagate(Effort e) override;
209 Node explain(TNode n, eq::EqProof* proof);
210 Node explain(TNode n) override;
211
212 /////////////////////////////////////////////////////////////////////////////
213 // SHARING
214 /////////////////////////////////////////////////////////////////////////////
215
216 private:
217 class MayEqualNotifyClass {
218 public:
notify(TNode propagation)219 bool notify(TNode propagation) { return true; }
notify(TNode t1,TNode t2)220 void notify(TNode t1, TNode t2) { }
221 };
222
223 /** The notify class for d_mayEqualEqualityEngine */
224 MayEqualNotifyClass d_mayEqualNotify;
225
226 /** Equaltity engine for determining if two arrays might be equal */
227 eq::EqualityEngine d_mayEqualEqualityEngine;
228
229 // Helper for computeCareGraph
230 void checkPair(TNode r1, TNode r2);
231
232 public:
233 void addSharedTerm(TNode t) override;
234 EqualityStatus getEqualityStatus(TNode a, TNode b) override;
235 void computeCareGraph() override;
isShared(TNode t)236 bool isShared(TNode t)
237 {
238 return (d_sharedArrays.find(t) != d_sharedArrays.end());
239 }
240
241 /////////////////////////////////////////////////////////////////////////////
242 // MODEL GENERATION
243 /////////////////////////////////////////////////////////////////////////////
244
245 public:
246 bool collectModelInfo(TheoryModel* m) override;
247
248 /////////////////////////////////////////////////////////////////////////////
249 // NOTIFICATIONS
250 /////////////////////////////////////////////////////////////////////////////
251
252
253 void presolve() override;
shutdown()254 void shutdown() override {}
255
256 /////////////////////////////////////////////////////////////////////////////
257 // MAIN SOLVER
258 /////////////////////////////////////////////////////////////////////////////
259
260 public:
261 void check(Effort e) override;
262
263 private:
264 TNode weakEquivGetRep(TNode node);
265 TNode weakEquivGetRepIndex(TNode node, TNode index);
266 void visitAllLeaves(TNode reason, std::vector<TNode>& conjunctions);
267 void weakEquivBuildCond(TNode node, TNode index, std::vector<TNode>& conjunctions);
268 void weakEquivMakeRep(TNode node);
269 void weakEquivMakeRepIndex(TNode node);
270 void weakEquivAddSecondary(TNode index, TNode arrayFrom, TNode arrayTo, TNode reason);
271 void checkWeakEquiv(bool arraysMerged);
272
273 // NotifyClass: template helper class for d_equalityEngine - handles call-back from congruence closure module
274 class NotifyClass : public eq::EqualityEngineNotify {
275 TheoryArrays& d_arrays;
276 public:
NotifyClass(TheoryArrays & arrays)277 NotifyClass(TheoryArrays& arrays): d_arrays(arrays) {}
278
eqNotifyTriggerEquality(TNode equality,bool value)279 bool eqNotifyTriggerEquality(TNode equality, bool value) override
280 {
281 Debug("arrays::propagate") << spaces(d_arrays.getSatContext()->getLevel()) << "NotifyClass::eqNotifyTriggerEquality(" << equality << ", " << (value ? "true" : "false") << ")" << std::endl;
282 // Just forward to arrays
283 if (value) {
284 return d_arrays.propagate(equality);
285 } else {
286 return d_arrays.propagate(equality.notNode());
287 }
288 }
289
eqNotifyTriggerPredicate(TNode predicate,bool value)290 bool eqNotifyTriggerPredicate(TNode predicate, bool value) override
291 {
292 Debug("arrays::propagate") << spaces(d_arrays.getSatContext()->getLevel()) << "NotifyClass::eqNotifyTriggerEquality(" << predicate << ", " << (value ? "true" : "false") << ")" << std::endl;
293 // Just forward to arrays
294 if (value) {
295 return d_arrays.propagate(predicate);
296 } else {
297 return d_arrays.propagate(predicate.notNode());
298 }
299 }
300
eqNotifyTriggerTermEquality(TheoryId tag,TNode t1,TNode t2,bool value)301 bool eqNotifyTriggerTermEquality(TheoryId tag,
302 TNode t1,
303 TNode t2,
304 bool value) override
305 {
306 Debug("arrays::propagate") << spaces(d_arrays.getSatContext()->getLevel()) << "NotifyClass::eqNotifyTriggerTermEquality(" << t1 << ", " << t2 << ", " << (value ? "true" : "false") << ")" << std::endl;
307 if (value) {
308 if (t1.getType().isArray()) {
309 if (!d_arrays.isShared(t1) || !d_arrays.isShared(t2)) {
310 return true;
311 }
312 }
313 // Propagate equality between shared terms
314 return d_arrays.propagate(t1.eqNode(t2));
315 } else {
316 if (t1.getType().isArray()) {
317 if (!d_arrays.isShared(t1) || !d_arrays.isShared(t2)) {
318 return true;
319 }
320 }
321 return d_arrays.propagate(t1.eqNode(t2).notNode());
322 }
323 return true;
324 }
325
eqNotifyConstantTermMerge(TNode t1,TNode t2)326 void eqNotifyConstantTermMerge(TNode t1, TNode t2) override
327 {
328 Debug("arrays::propagate") << spaces(d_arrays.getSatContext()->getLevel()) << "NotifyClass::eqNotifyConstantTermMerge(" << t1 << ", " << t2 << ")" << std::endl;
329 d_arrays.conflict(t1, t2);
330 }
331
eqNotifyNewClass(TNode t)332 void eqNotifyNewClass(TNode t) override {}
eqNotifyPreMerge(TNode t1,TNode t2)333 void eqNotifyPreMerge(TNode t1, TNode t2) override {}
eqNotifyPostMerge(TNode t1,TNode t2)334 void eqNotifyPostMerge(TNode t1, TNode t2) override
335 {
336 if (t1.getType().isArray()) {
337 d_arrays.mergeArrays(t1, t2);
338 }
339 }
eqNotifyDisequal(TNode t1,TNode t2,TNode reason)340 void eqNotifyDisequal(TNode t1, TNode t2, TNode reason) override {}
341 };
342
343 /** The notify class for d_equalityEngine */
344 NotifyClass d_notify;
345
346 /** Equaltity engine */
347 eq::EqualityEngine d_equalityEngine;
348
349 /** Are we in conflict? */
350 context::CDO<bool> d_conflict;
351
352 /** Conflict when merging constants */
353 void conflict(TNode a, TNode b);
354
355 /** The conflict node */
356 Node d_conflictNode;
357
358 /**
359 * Context dependent map from a congruence class canonical representative of
360 * type array to an Info pointer that keeps track of information useful to axiom
361 * instantiation
362 */
363
364 Backtracker<TNode> d_backtracker;
365 ArrayInfo d_infoMap;
366
367 context::CDQueue<Node> d_mergeQueue;
368
369 bool d_mergeInProgress;
370
371 using RowLemmaType = std::tuple<TNode, TNode, TNode, TNode>;
372
373 context::CDQueue<RowLemmaType> d_RowQueue;
374 context::CDHashSet<RowLemmaType, RowLemmaTypeHashFunction > d_RowAlreadyAdded;
375
376 typedef context::CDHashSet<Node, NodeHashFunction> CDNodeSet;
377
378 CDNodeSet d_sharedArrays;
379 CDNodeSet d_sharedOther;
380 context::CDO<bool> d_sharedTerms;
381
382 // Map from constant values to read terms that read from that values equal to that constant value in the current model
383 // When a new read term is created, we check the index to see if we know the model value. If so, we add it to d_constReads (and d_constReadsList)
384 // If not, we push it onto d_reads and figure out where it goes at computeCareGraph time.
385 // d_constReadsList is used as a backup in case we can't compute the model at computeCareGraph time.
386 typedef std::unordered_map<Node, CTNodeList*, NodeHashFunction> CNodeNListMap;
387 CNodeNListMap d_constReads;
388 context::CDList<TNode> d_reads;
389 context::CDList<TNode> d_constReadsList;
390 context::Context* d_constReadsContext;
391 /** Helper class to keep d_constReadsContext in sync with satContext */
392 class ContextPopper : public context::ContextNotifyObj {
393 context::Context* d_satContext;
394 context::Context* d_contextToPop;
395 protected:
contextNotifyPop()396 void contextNotifyPop() override
397 {
398 if (d_contextToPop->getLevel() > d_satContext->getLevel())
399 {
400 d_contextToPop->pop();
401 }
402 }
403 public:
ContextPopper(context::Context * context,context::Context * contextToPop)404 ContextPopper(context::Context* context, context::Context* contextToPop)
405 :context::ContextNotifyObj(context), d_satContext(context),
406 d_contextToPop(contextToPop)
407 {}
408
409 };/* class ContextPopper */
410 ContextPopper d_contextPopper;
411
412 std::unordered_map<Node, Node, NodeHashFunction> d_skolemCache;
413 context::CDO<unsigned> d_skolemIndex;
414 std::vector<Node> d_skolemAssertions;
415
416 // The decision requests we have for the core
417 context::CDQueue<Node> d_decisionRequests;
418
419 // List of nodes that need permanent references in this context
420 context::CDList<Node> d_permRef;
421 context::CDList<Node> d_modelConstraints;
422 context::CDHashSet<Node, NodeHashFunction > d_lemmasSaved;
423 std::vector<Node> d_lemmas;
424
425 // Default values for each mayEqual equivalence class
426 typedef context::CDHashMap<Node,Node,NodeHashFunction> DefValMap;
427 DefValMap d_defValues;
428
429 typedef std::unordered_map<std::pair<TNode, TNode>, CTNodeList*, TNodePairHashFunction> ReadBucketMap;
430 ReadBucketMap d_readBucketTable;
431 context::Context* d_readTableContext;
432 context::CDList<Node> d_arrayMerges;
433 std::vector<CTNodeList*> d_readBucketAllocations;
434
435 Node getSkolem(TNode ref, const std::string& name, const TypeNode& type, const std::string& comment, bool makeEqual = true);
436 Node mkAnd(std::vector<TNode>& conjunctions, bool invert = false, unsigned startIndex = 0);
437 void setNonLinear(TNode a);
438 void checkRIntro1(TNode a, TNode b);
439 Node removeRepLoops(TNode a, TNode rep);
440 Node expandStores(TNode s, std::vector<TNode>& assumptions, bool checkLoop = false, TNode a = TNode(), TNode b = TNode());
441 void mergeArrays(TNode a, TNode b);
442 void checkStore(TNode a);
443 void checkRowForIndex(TNode i, TNode a);
444 void checkRowLemmas(TNode a, TNode b);
445 void propagate(RowLemmaType lem);
446 void queueRowLemma(RowLemmaType lem);
447 bool dischargeLemmas();
448
449 std::vector<Node> d_decisions;
450 bool d_inCheckModel;
451 int d_topLevel;
452
453 /** An equality-engine callback for proof reconstruction */
454 ArrayProofReconstruction d_proofReconstruction;
455
456 /**
457 * The decision strategy for the theory of arrays, which calls the
458 * getNextDecisionEngineRequest function below.
459 */
460 class TheoryArraysDecisionStrategy : public DecisionStrategy
461 {
462 public:
463 TheoryArraysDecisionStrategy(TheoryArrays* ta);
464 /** initialize */
465 void initialize() override;
466 /** get next decision request */
467 Node getNextDecisionRequest() override;
468 /** identify */
469 std::string identify() const override;
470
471 private:
472 /** pointer to the theory of arrays */
473 TheoryArrays* d_ta;
474 };
475 /** an instance of the above decision strategy */
476 std::unique_ptr<TheoryArraysDecisionStrategy> d_dstrat;
477 /** get the next decision request
478 *
479 * If the "arrays-eager-index" option is enabled, then whenever a
480 * read-over-write lemma is generated, a decision request is also generated
481 * for the comparison between the indexes that appears in the lemma.
482 */
483 Node getNextDecisionRequest();
484
485 public:
getEqualityEngine()486 eq::EqualityEngine* getEqualityEngine() override { return &d_equalityEngine; }
487
488 };/* class TheoryArrays */
489
490 }/* CVC4::theory::arrays namespace */
491 }/* CVC4::theory namespace */
492 }/* CVC4 namespace */
493
494 #endif /* __CVC4__THEORY__ARRAYS__THEORY_ARRAYS_H */
495