/* Derivation and subsumption rules for constraints. Copyright (C) 2013-2021 Free Software Foundation, Inc. Contributed by Andrew Sutton (andrew.n.sutton@gmail.com) This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ #include "config.h" #define INCLUDE_LIST #include "system.h" #include "coretypes.h" #include "tm.h" #include "timevar.h" #include "hash-set.h" #include "machmode.h" #include "vec.h" #include "double-int.h" #include "input.h" #include "alias.h" #include "symtab.h" #include "wide-int.h" #include "inchash.h" #include "tree.h" #include "stringpool.h" #include "attribs.h" #include "intl.h" #include "flags.h" #include "cp-tree.h" #include "c-family/c-common.h" #include "c-family/c-objc.h" #include "cp-objcp-common.h" #include "tree-inline.h" #include "decl.h" #include "toplev.h" #include "type-utils.h" /* A conjunctive or disjunctive clause. Each clause maintains an iterator that refers to the current term, which is used in the linear decomposition of a formula into CNF or DNF. */ struct clause { typedef std::list::iterator iterator; typedef std::list::const_iterator const_iterator; /* Initialize a clause with an initial term. */ clause (tree t) { m_terms.push_back (t); if (TREE_CODE (t) == ATOMIC_CONSTR) m_set.add (t); m_current = m_terms.begin (); } /* Create a copy of the current term. The current iterator is set to point to the same position in the copied list of terms. */ clause (clause const& c) : m_terms (c.m_terms), m_set (c.m_set), m_current (m_terms.begin ()) { std::advance (m_current, std::distance (c.begin (), c.current ())); } /* Returns true when all terms are atoms. */ bool done () const { return m_current == end (); } /* Advance to the next term. */ void advance () { gcc_assert (!done ()); ++m_current; } /* Replaces the current term at position ITER with T. If T is an atomic constraint that already appears in the clause, remove but do not replace ITER. Returns a pair containing an iterator to the replace object or past the erased object and a boolean value which is true if an object was erased. */ std::pair replace (iterator iter, tree t) { gcc_assert (TREE_CODE (*iter) != ATOMIC_CONSTR); if (TREE_CODE (t) == ATOMIC_CONSTR) { if (m_set.add (t)) return std::make_pair (m_terms.erase (iter), true); } *iter = t; return std::make_pair (iter, false); } /* Inserts T before ITER in the list of terms. If T has already is an atomic constraint that already appears in the clause, no action is taken, and the current iterator is returned. Returns a pair of an iterator to the inserted object or ITER if no insertion occurred and a boolean value which is true if an object was inserted. */ std::pair insert (iterator iter, tree t) { if (TREE_CODE (t) == ATOMIC_CONSTR) { if (m_set.add (t)) return std::make_pair (iter, false); } return std::make_pair (m_terms.insert (iter, t), true); } /* Replaces the current term with T. In the case where the current term is erased (because T is redundant), update the position of the current term to the next term. */ void replace (tree t) { m_current = replace (m_current, t).first; } /* Replace the current term with T1 and T2, in that order. */ void replace (tree t1, tree t2) { /* Replace the current term with t1. Ensure that iter points to the term before which t2 will be inserted. Update the current term as needed. */ std::pair rep = replace (m_current, t1); if (rep.second) m_current = rep.first; else ++rep.first; /* Insert the t2. Make this the current term if we erased the prior term. */ std::pair ins = insert (rep.first, t2); if (rep.second && ins.second) m_current = ins.first; } /* Returns true if the clause contains the term T. */ bool contains (tree t) { gcc_assert (TREE_CODE (t) == ATOMIC_CONSTR); return m_set.contains (t); } /* Returns an iterator to the first clause in the formula. */ iterator begin () { return m_terms.begin (); } /* Returns an iterator to the first clause in the formula. */ const_iterator begin () const { return m_terms.begin (); } /* Returns an iterator past the last clause in the formula. */ iterator end () { return m_terms.end (); } /* Returns an iterator past the last clause in the formula. */ const_iterator end () const { return m_terms.end (); } /* Returns the current iterator. */ const_iterator current () const { return m_current; } std::list m_terms; /* The list of terms. */ hash_set m_set; /* The set of atomic constraints. */ iterator m_current; /* The current term. */ }; /* A proof state owns a list of goals and tracks the current sub-goal. The class also provides facilities for managing subgoals and constructing term lists. */ struct formula { typedef std::list::iterator iterator; typedef std::list::const_iterator const_iterator; /* Construct a formula with an initial formula in a single clause. */ formula (tree t) { m_clauses.emplace_back (t); m_current = m_clauses.begin (); } /* Returns true when all clauses are atomic. */ bool done () const { return m_current == end (); } /* Advance to the next term. */ void advance () { gcc_assert (!done ()); ++m_current; } /* Insert a copy of clause into the formula. This corresponds to a distribution of one logical operation over the other. */ clause& branch () { gcc_assert (!done ()); return *m_clauses.insert (std::next (m_current), *m_current); } /* Returns the position of the current clause. */ iterator current () { return m_current; } /* Returns an iterator to the first clause in the formula. */ iterator begin () { return m_clauses.begin (); } /* Returns an iterator to the first clause in the formula. */ const_iterator begin () const { return m_clauses.begin (); } /* Returns an iterator past the last clause in the formula. */ iterator end () { return m_clauses.end (); } /* Returns an iterator past the last clause in the formula. */ const_iterator end () const { return m_clauses.end (); } /* Remove the specified clause from the formula. */ void erase (iterator i) { gcc_assert (i != m_current); m_clauses.erase (i); } std::list m_clauses; /* The list of clauses. */ iterator m_current; /* The current clause. */ }; void debug (clause& c) { for (clause::iterator i = c.begin(); i != c.end(); ++i) verbatim (" # %E", *i); } void debug (formula& f) { for (formula::iterator i = f.begin(); i != f.end(); ++i) { /* Format punctuators via %s to avoid -Wformat-diag. */ verbatim ("%s", "((("); debug (*i); verbatim ("%s", ")))"); } } /* The logical rules used to analyze a logical formula. The "left" and "right" refer to the position of formula in a sequent (as in sequent calculus). */ enum rules { left, right }; /* Distribution counting. */ static inline bool disjunction_p (tree t) { return TREE_CODE (t) == DISJ_CONSTR; } static inline bool conjunction_p (tree t) { return TREE_CODE (t) == CONJ_CONSTR; } static inline bool atomic_p (tree t) { return TREE_CODE (t) == ATOMIC_CONSTR; } /* Recursively count the number of clauses produced when converting T to DNF. Returns a pair containing the number of clauses and a bool value signifying that the tree would be rewritten as a result of distributing. In general, a conjunction for which this flag is set is considered a disjunction for the purpose of counting. */ static std::pair dnf_size_r (tree t) { if (atomic_p (t)) /* Atomic constraints produce no clauses. */ return std::make_pair (0, false); /* For compound constraints, recursively count clauses and unpack the results. */ tree lhs = TREE_OPERAND (t, 0); tree rhs = TREE_OPERAND (t, 1); std::pair p1 = dnf_size_r (lhs); std::pair p2 = dnf_size_r (rhs); int n1 = p1.first, n2 = p2.first; bool d1 = p1.second, d2 = p2.second; if (disjunction_p (t)) { /* Matches constraints of the form P \/ Q. Disjunctions contribute linearly to the number of constraints. When both P and Q are disjunctions, clauses are added. When only one of P and Q is a disjunction, an additional clause is produced. When neither P nor Q are disjunctions, two clauses are produced. */ if (disjunction_p (lhs)) { if (disjunction_p (rhs) || (conjunction_p (rhs) && d2)) /* Both P and Q are disjunctions. */ return std::make_pair (n1 + n2, d1 | d2); else /* Only LHS is a disjunction. */ return std::make_pair (1 + n1 + n2, d1 | d2); gcc_unreachable (); } if (conjunction_p (lhs)) { if ((disjunction_p (rhs) && d1) || (conjunction_p (rhs) && d1 && d2)) /* Both P and Q are disjunctions. */ return std::make_pair (n1 + n2, d1 | d2); if (disjunction_p (rhs) || (conjunction_p (rhs) && d1 != d2) || (atomic_p (rhs) && d1)) /* Either LHS or RHS is a disjunction. */ return std::make_pair (1 + n1 + n2, d1 | d2); else /* Neither LHS nor RHS is a disjunction. */ return std::make_pair (2, false); } if (atomic_p (lhs)) { if (disjunction_p (rhs) || (conjunction_p (rhs) && d2)) /* Only RHS is a disjunction. */ return std::make_pair (1 + n1 + n2, d1 | d2); else /* Neither LHS nor RHS is a disjunction. */ return std::make_pair (2, false); } } else /* conjunction_p (t) */ { /* Matches constraints of the form P /\ Q, possibly resulting in the distribution of one side over the other. When both P and Q are disjunctions, the number of clauses are multiplied. When only one of P and Q is a disjunction, the number of clauses are added. Otherwise, neither side is a disjunction and no clauses are created. */ if (disjunction_p (lhs)) { if (disjunction_p (rhs) || (conjunction_p (rhs) && d2)) /* Both P and Q are disjunctions. */ return std::make_pair (n1 * n2, true); else /* Only LHS is a disjunction. */ return std::make_pair (n1 + n2, true); gcc_unreachable (); } if (conjunction_p (lhs)) { if ((disjunction_p (rhs) && d1) || (conjunction_p (rhs) && d1 && d2)) /* Both P and Q are disjunctions. */ return std::make_pair (n1 * n2, true); if (disjunction_p (rhs) || (conjunction_p (rhs) && d1 != d2) || (atomic_p (rhs) && d1)) /* Either LHS or RHS is a disjunction. */ return std::make_pair (n1 + n2, true); else /* Neither LHS nor RHS is a disjunction. */ return std::make_pair (0, false); } if (atomic_p (lhs)) { if (disjunction_p (rhs) || (conjunction_p (rhs) && d2)) /* Only RHS is a disjunction. */ return std::make_pair (n1 + n2, true); else /* Neither LHS nor RHS is a disjunction. */ return std::make_pair (0, false); } } gcc_unreachable (); } /* Recursively count the number of clauses produced when converting T to CNF. Returns a pair containing the number of clauses and a bool value signifying that the tree would be rewritten as a result of distributing. In general, a disjunction for which this flag is set is considered a conjunction for the purpose of counting. */ static std::pair cnf_size_r (tree t) { if (atomic_p (t)) /* Atomic constraints produce no clauses. */ return std::make_pair (0, false); /* For compound constraints, recursively count clauses and unpack the results. */ tree lhs = TREE_OPERAND (t, 0); tree rhs = TREE_OPERAND (t, 1); std::pair p1 = cnf_size_r (lhs); std::pair p2 = cnf_size_r (rhs); int n1 = p1.first, n2 = p2.first; bool d1 = p1.second, d2 = p2.second; if (disjunction_p (t)) { /* Matches constraints of the form P \/ Q, possibly resulting in the distribution of one side over the other. When both P and Q are conjunctions, the number of clauses are multiplied. When only one of P and Q is a conjunction, the number of clauses are added. Otherwise, neither side is a conjunction and no clauses are created. */ if (disjunction_p (lhs)) { if ((disjunction_p (rhs) && d1 && d2) || (conjunction_p (rhs) && d1)) /* Both P and Q are conjunctions. */ return std::make_pair (n1 * n2, true); if ((disjunction_p (rhs) && d1 != d2) || conjunction_p (rhs) || (atomic_p (rhs) && d1)) /* Either LHS or RHS is a conjunction. */ return std::make_pair (n1 + n2, true); else /* Neither LHS nor RHS is a conjunction. */ return std::make_pair (0, false); } if (conjunction_p (lhs)) { if ((disjunction_p (rhs) && d2) || conjunction_p (rhs)) /* Both LHS and RHS are conjunctions. */ return std::make_pair (n1 * n2, true); else /* Only LHS is a conjunction. */ return std::make_pair (n1 + n2, true); } if (atomic_p (lhs)) { if ((disjunction_p (rhs) && d2) || conjunction_p (rhs)) /* Only RHS is a disjunction. */ return std::make_pair (n1 + n2, true); else /* Neither LHS nor RHS is a disjunction. */ return std::make_pair (0, false); } } else /* conjunction_p (t) */ { /* Matches constraints of the form P /\ Q. Conjunctions contribute linearly to the number of constraints. When both P and Q are conjunctions, clauses are added. When only one of P and Q is a conjunction, an additional clause is produced. When neither P nor Q are conjunctions, two clauses are produced. */ if (disjunction_p (lhs)) { if ((disjunction_p (rhs) && d1 && d2) || (conjunction_p (rhs) && d1)) /* Both P and Q are conjunctions. */ return std::make_pair (n1 + n2, d1 | d2); if ((disjunction_p (rhs) && d1 != d2) || conjunction_p (rhs) || (atomic_p (rhs) && d1)) /* Either LHS or RHS is a conjunction. */ return std::make_pair (1 + n1 + n2, d1 | d2); else /* Neither LHS nor RHS is a conjunction. */ return std::make_pair (2, false); } if (conjunction_p (lhs)) { if ((disjunction_p (rhs) && d2) || conjunction_p (rhs)) /* Both LHS and RHS are conjunctions. */ return std::make_pair (n1 + n2, d1 | d2); else /* Only LHS is a conjunction. */ return std::make_pair (1 + n1 + n2, d1 | d2); } if (atomic_p (lhs)) { if ((disjunction_p (rhs) && d2) || conjunction_p (rhs)) /* Only RHS is a disjunction. */ return std::make_pair (1 + n1 + n2, d1 | d2); else /* Neither LHS nor RHS is a disjunction. */ return std::make_pair (2, false); } } gcc_unreachable (); } /* Count the number conjunctive clauses that would be created when rewriting T to DNF. */ static int dnf_size (tree t) { std::pair result = dnf_size_r (t); return result.first == 0 ? 1 : result.first; } /* Count the number disjunctive clauses that would be created when rewriting T to CNF. */ static int cnf_size (tree t) { std::pair result = cnf_size_r (t); return result.first == 0 ? 1 : result.first; } /* A left-conjunction is replaced by its operands. */ void replace_term (clause& c, tree t) { tree t1 = TREE_OPERAND (t, 0); tree t2 = TREE_OPERAND (t, 1); return c.replace (t1, t2); } /* Create a new clause in the formula by copying the current clause. In the current clause, the term at CI is replaced by the first operand, and in the new clause, it is replaced by the second. */ void branch_clause (formula& f, clause& c1, tree t) { tree t1 = TREE_OPERAND (t, 0); tree t2 = TREE_OPERAND (t, 1); clause& c2 = f.branch (); c1.replace (t1); c2.replace (t2); } /* Decompose t1 /\ t2 according to the rules R. */ inline void decompose_conjuntion (formula& f, clause& c, tree t, rules r) { if (r == left) replace_term (c, t); else branch_clause (f, c, t); } /* Decompose t1 \/ t2 according to the rules R. */ inline void decompose_disjunction (formula& f, clause& c, tree t, rules r) { if (r == right) replace_term (c, t); else branch_clause (f, c, t); } /* An atomic constraint is already decomposed. */ inline void decompose_atom (clause& c) { c.advance (); } /* Decompose a term of clause C (in formula F) according to the logical rules R. */ void decompose_term (formula& f, clause& c, tree t, rules r) { switch (TREE_CODE (t)) { case CONJ_CONSTR: return decompose_conjuntion (f, c, t, r); case DISJ_CONSTR: return decompose_disjunction (f, c, t, r); default: return decompose_atom (c); } } /* Decompose C (in F) using the logical rules R until it is comprised of only atomic constraints. */ void decompose_clause (formula& f, clause& c, rules r) { while (!c.done ()) decompose_term (f, c, *c.current (), r); f.advance (); } static bool derive_proof (clause&, tree, rules); /* Derive a proof of both operands of T. */ static bool derive_proof_for_both_operands (clause& c, tree t, rules r) { if (!derive_proof (c, TREE_OPERAND (t, 0), r)) return false; return derive_proof (c, TREE_OPERAND (t, 1), r); } /* Derive a proof of either operand of T. */ static bool derive_proof_for_either_operand (clause& c, tree t, rules r) { if (derive_proof (c, TREE_OPERAND (t, 0), r)) return true; return derive_proof (c, TREE_OPERAND (t, 1), r); } /* Derive a proof of the atomic constraint T in clause C. */ static bool derive_atomic_proof (clause& c, tree t) { return c.contains (t); } /* Derive a proof of T from the terms in C. */ static bool derive_proof (clause& c, tree t, rules r) { switch (TREE_CODE (t)) { case CONJ_CONSTR: if (r == left) return derive_proof_for_both_operands (c, t, r); else return derive_proof_for_either_operand (c, t, r); case DISJ_CONSTR: if (r == left) return derive_proof_for_either_operand (c, t, r); else return derive_proof_for_both_operands (c, t, r); default: return derive_atomic_proof (c, t); } } /* Key/value pair for caching subsumption results. This associates a pair of constraints with a boolean value indicating the result. */ struct GTY((for_user)) subsumption_entry { tree lhs; tree rhs; bool result; }; /* Hashing function and equality for constraint entries. */ struct subsumption_hasher : ggc_ptr_hash { static hashval_t hash (subsumption_entry *e) { hashval_t val = 0; val = iterative_hash_constraint (e->lhs, val); val = iterative_hash_constraint (e->rhs, val); return val; } static bool equal (subsumption_entry *e1, subsumption_entry *e2) { if (!constraints_equivalent_p (e1->lhs, e2->lhs)) return false; if (!constraints_equivalent_p (e1->rhs, e2->rhs)) return false; return true; } }; /* Caches the results of subsumes_non_null(t1, t1). */ static GTY ((deletable)) hash_table *subsumption_cache; /* Search for a previously cached subsumption result. */ static bool* lookup_subsumption (tree t1, tree t2) { if (!subsumption_cache) return NULL; subsumption_entry elt = { t1, t2, false }; subsumption_entry* found = subsumption_cache->find (&elt); if (found) return &found->result; else return 0; } /* Save a subsumption result. */ static bool save_subsumption (tree t1, tree t2, bool result) { if (!subsumption_cache) subsumption_cache = hash_table::create_ggc(31); subsumption_entry elt = {t1, t2, result}; subsumption_entry** slot = subsumption_cache->find_slot (&elt, INSERT); subsumption_entry* entry = ggc_alloc (); *entry = elt; *slot = entry; return result; } /* Returns true if the LEFT constraint subsume the RIGHT constraints. This is done by deriving a proof of the conclusions on the RIGHT from the assumptions on the LEFT assumptions. */ static bool subsumes_constraints_nonnull (tree lhs, tree rhs) { auto_timevar time (TV_CONSTRAINT_SUB); if (bool *b = lookup_subsumption(lhs, rhs)) return *b; tree x, y; rules r; if (dnf_size (lhs) <= cnf_size (rhs)) /* When LHS looks simpler than RHS, we'll determine subsumption by decomposing LHS into its disjunctive normal form and checking that each (conjunctive) clause in the decomposed LHS implies RHS. */ x = lhs, y = rhs, r = left; else /* Otherwise, we'll determine subsumption by decomposing RHS into its conjunctive normal form and checking that each (disjunctive) clause in the decomposed RHS implies LHS. */ x = rhs, y = lhs, r = right; /* Decompose X into a list of sequents according to R, and recursively check for implication of Y. */ bool result = true; formula f (x); while (!f.done ()) { auto i = f.current (); decompose_clause (f, *i, r); if (!derive_proof (*i, y, r)) { result = false; break; } f.erase (i); } return save_subsumption (lhs, rhs, result); } /* Returns true if the LEFT constraints subsume the RIGHT constraints. */ bool subsumes (tree lhs, tree rhs) { if (lhs == rhs) return true; if (!lhs || lhs == error_mark_node) return false; if (!rhs || rhs == error_mark_node) return true; return subsumes_constraints_nonnull (lhs, rhs); } #include "gt-cp-logic.h"