| /* Tracking equivalence classes and constraints at a point on an execution path. |
| Copyright (C) 2019-2022 Free Software Foundation, Inc. |
| Contributed by David Malcolm <dmalcolm@redhat.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 |
| <http://www.gnu.org/licenses/>. */ |
| |
| #ifndef GCC_ANALYZER_CONSTRAINT_MANAGER_H |
| #define GCC_ANALYZER_CONSTRAINT_MANAGER_H |
| |
| namespace ana { |
| |
| class constraint_manager; |
| |
| enum bound_kind |
| { |
| BK_LOWER, |
| BK_UPPER |
| }; |
| |
| /* One of the end-points of a range. */ |
| |
| struct bound |
| { |
| bound () : m_constant (NULL_TREE), m_closed (false) {} |
| bound (tree constant, bool closed) |
| : m_constant (constant), m_closed (closed) {} |
| |
| void ensure_closed (enum bound_kind bound_kind); |
| |
| const char * get_relation_as_str () const; |
| |
| tree m_constant; |
| bool m_closed; |
| }; |
| |
| /* A range of values, used for determining if a value has been |
| constrained to just one possible constant value. */ |
| |
| class range |
| { |
| public: |
| range () : m_lower_bound (), m_upper_bound () {} |
| range (const bound &lower, const bound &upper) |
| : m_lower_bound (lower), m_upper_bound (upper) {} |
| |
| void dump_to_pp (pretty_printer *pp) const; |
| void dump () const; |
| |
| tree constrained_to_single_element (); |
| |
| tristate eval_condition (enum tree_code op, |
| tree rhs_const) const; |
| bool below_lower_bound (tree rhs_const) const; |
| bool above_upper_bound (tree rhs_const) const; |
| |
| bool add_bound (bound b, enum bound_kind bound_kind); |
| bool add_bound (enum tree_code op, tree rhs_const); |
| |
| private: |
| bound m_lower_bound; |
| bound m_upper_bound; |
| }; |
| |
| /* A closed range of values with constant integer bounds |
| e.g. [3, 5] for the set {3, 4, 5}. */ |
| |
| struct bounded_range |
| { |
| bounded_range (const_tree lower, const_tree upper); |
| |
| void dump_to_pp (pretty_printer *pp, bool show_types) const; |
| void dump (bool show_types) const; |
| |
| json::object *to_json () const; |
| |
| bool contains_p (tree cst) const; |
| |
| bool intersects_p (const bounded_range &other, |
| bounded_range *out) const; |
| |
| bool operator== (const bounded_range &other) const; |
| bool operator!= (const bounded_range &other) const |
| { |
| return !(*this == other); |
| } |
| |
| static int cmp (const bounded_range &a, const bounded_range &b); |
| |
| tree m_lower; |
| tree m_upper; |
| |
| private: |
| static void set_json_attr (json::object *obj, const char *name, tree value); |
| }; |
| |
| /* A collection of bounded_range instances, suitable |
| for representing the ranges on a case label within a switch |
| statement. */ |
| |
| struct bounded_ranges |
| { |
| public: |
| typedef bounded_ranges key_t; |
| |
| bounded_ranges (const bounded_range &range); |
| bounded_ranges (const vec<bounded_range> &ranges); |
| bounded_ranges (enum tree_code op, tree rhs_const); |
| |
| bool operator== (const bounded_ranges &other) const; |
| |
| hashval_t get_hash () const { return m_hash; } |
| |
| void dump_to_pp (pretty_printer *pp, bool show_types) const; |
| void dump (bool show_types) const; |
| |
| json::value *to_json () const; |
| |
| tristate eval_condition (enum tree_code op, |
| tree rhs_const, |
| bounded_ranges_manager *mgr) const; |
| |
| bool contain_p (tree cst) const; |
| bool empty_p () const { return m_ranges.length () == 0; } |
| |
| static int cmp (const bounded_ranges *a, const bounded_ranges *b); |
| |
| private: |
| void canonicalize (); |
| void validate () const; |
| |
| friend class bounded_ranges_manager; |
| |
| auto_vec<bounded_range> m_ranges; |
| hashval_t m_hash; |
| }; |
| |
| } // namespace ana |
| |
| template <> struct default_hash_traits<bounded_ranges::key_t> |
| : public member_function_hash_traits<bounded_ranges::key_t> |
| { |
| static const bool empty_zero_p = true; |
| }; |
| |
| namespace ana { |
| |
| /* An object to own and consolidate bounded_ranges instances. |
| This also caches the mapping from switch_cfg_superedge |
| bounded_ranges instances, so that get_or_create_ranges_for_switch is |
| memoized. */ |
| |
| class bounded_ranges_manager |
| { |
| public: |
| ~bounded_ranges_manager (); |
| |
| const bounded_ranges * |
| get_or_create_ranges_for_switch (const switch_cfg_superedge *edge, |
| const gswitch *switch_stmt); |
| |
| const bounded_ranges *get_or_create_empty (); |
| const bounded_ranges *get_or_create_point (const_tree value); |
| const bounded_ranges *get_or_create_range (const_tree lower_bound, |
| const_tree upper_bound); |
| const bounded_ranges * |
| get_or_create_union (const vec <const bounded_ranges *> &others); |
| const bounded_ranges * |
| get_or_create_intersection (const bounded_ranges *a, |
| const bounded_ranges *b); |
| const bounded_ranges * |
| get_or_create_inverse (const bounded_ranges *other, tree type); |
| |
| void log_stats (logger *logger, bool show_objs) const; |
| |
| private: |
| const bounded_ranges * |
| create_ranges_for_switch (const switch_cfg_superedge &edge, |
| const gswitch *switch_stmt); |
| |
| const bounded_ranges * |
| make_case_label_ranges (const gswitch *switch_stmt, |
| tree case_label); |
| |
| const bounded_ranges *consolidate (bounded_ranges *); |
| |
| struct hash_traits_t : public typed_noop_remove<bounded_ranges *> |
| { |
| typedef bounded_ranges *key_type; |
| typedef bounded_ranges *value_type; |
| |
| static inline bool |
| equal (const key_type &k1, const key_type &k2) |
| { |
| return *k1 == *k2; |
| } |
| static inline hashval_t |
| hash (const key_type &k) |
| { |
| return k->get_hash (); |
| } |
| static inline bool is_empty (key_type k) { return k == NULL; } |
| static inline void mark_empty (key_type &k) { k = NULL; } |
| static inline bool is_deleted (key_type k) |
| { |
| return k == reinterpret_cast<key_type> (1); |
| } |
| |
| static const bool empty_zero_p = true; |
| }; |
| struct traits_t : public simple_hashmap_traits<hash_traits_t, |
| bounded_ranges *> |
| { |
| }; |
| typedef hash_map<bounded_ranges *, bounded_ranges *, traits_t> map_t; |
| map_t m_map; |
| |
| typedef hash_map<const switch_cfg_superedge *, |
| const bounded_ranges *> edge_cache_t; |
| edge_cache_t m_edge_cache; |
| }; |
| |
| /* An equivalence class within a constraint manager: a set of |
| svalues that are known to all be equal to each other, |
| together with an optional tree constant that they are equal to. */ |
| |
| class equiv_class |
| { |
| public: |
| equiv_class (); |
| equiv_class (const equiv_class &other); |
| |
| hashval_t hash () const; |
| bool operator== (const equiv_class &other); |
| |
| void add (const svalue *sval); |
| bool del (const svalue *sval); |
| |
| tree get_any_constant () const { return m_constant; } |
| |
| const svalue *get_representative () const; |
| |
| void canonicalize (); |
| |
| void print (pretty_printer *pp) const; |
| |
| json::object *to_json () const; |
| |
| bool contains_non_constant_p () const; |
| |
| /* An equivalence class can contain multiple constants (e.g. multiple |
| different zeroes, for different types); these are just for the last |
| constant added. */ |
| tree m_constant; |
| const svalue *m_cst_sval; |
| |
| // TODO: should this be a set rather than a vec? |
| auto_vec<const svalue *> m_vars; |
| }; |
| |
| /* The various kinds of constraint. */ |
| |
| enum constraint_op |
| { |
| CONSTRAINT_NE, |
| CONSTRAINT_LT, |
| CONSTRAINT_LE |
| }; |
| |
| const char *constraint_op_code (enum constraint_op c_op); |
| |
| /* An ID for an equiv_class within a constraint_manager. Internally, this |
| is an index into a vector of equiv_class * within the constraint_manager. */ |
| |
| class equiv_class_id |
| { |
| public: |
| static equiv_class_id null () { return equiv_class_id (-1); } |
| |
| equiv_class_id (unsigned idx) : m_idx (idx) {} |
| const equiv_class &get_obj (const constraint_manager &cm) const; |
| equiv_class &get_obj (constraint_manager &cm) const; |
| |
| bool operator== (const equiv_class_id &other) const |
| { |
| return m_idx == other.m_idx; |
| } |
| bool operator!= (const equiv_class_id &other) const |
| { |
| return m_idx != other.m_idx; |
| } |
| |
| bool null_p () const { return m_idx == -1; } |
| |
| static equiv_class_id from_int (int idx) { return equiv_class_id (idx); } |
| int as_int () const { return m_idx; } |
| |
| void print (pretty_printer *pp) const; |
| |
| void update_for_removal (equiv_class_id other) |
| { |
| if (m_idx > other.m_idx) |
| m_idx--; |
| } |
| |
| int m_idx; |
| }; |
| |
| /* A relationship between two equivalence classes in a constraint_manager. */ |
| |
| class constraint |
| { |
| public: |
| constraint (equiv_class_id lhs, enum constraint_op c_op, equiv_class_id rhs) |
| : m_lhs (lhs), m_op (c_op), m_rhs (rhs) |
| { |
| gcc_assert (!lhs.null_p ()); |
| gcc_assert (!rhs.null_p ()); |
| } |
| |
| void print (pretty_printer *pp, const constraint_manager &cm) const; |
| |
| json::object *to_json () const; |
| |
| hashval_t hash () const; |
| bool operator== (const constraint &other) const; |
| |
| /* Is this an ordering, rather than a "!=". */ |
| bool is_ordering_p () const |
| { |
| return m_op != CONSTRAINT_NE; |
| } |
| |
| bool implied_by (const constraint &other, |
| const constraint_manager &cm) const; |
| |
| equiv_class_id m_lhs; |
| enum constraint_op m_op; |
| equiv_class_id m_rhs; |
| }; |
| |
| /* An abstract base class for use with constraint_manager::for_each_fact. */ |
| |
| class fact_visitor |
| { |
| public: |
| virtual ~fact_visitor () {} |
| virtual void on_fact (const svalue *lhs, |
| enum tree_code, |
| const svalue *rhs) = 0; |
| virtual void on_ranges (const svalue *lhs, |
| const bounded_ranges *ranges) = 0; |
| }; |
| |
| class bounded_ranges_constraint |
| { |
| public: |
| bounded_ranges_constraint (equiv_class_id ec_id, |
| const bounded_ranges *ranges) |
| : m_ec_id (ec_id), m_ranges (ranges) |
| { |
| } |
| |
| void print (pretty_printer *pp, const constraint_manager &cm) const; |
| |
| json::object *to_json () const; |
| |
| bool operator== (const bounded_ranges_constraint &other) const; |
| bool operator!= (const bounded_ranges_constraint &other) const |
| { |
| return !(*this == other); |
| } |
| |
| void add_to_hash (inchash::hash *hstate) const; |
| |
| equiv_class_id m_ec_id; |
| const bounded_ranges *m_ranges; |
| }; |
| |
| /* A collection of equivalence classes and constraints on them. |
| |
| Given N svalues, this can be thought of as representing a subset of |
| N-dimensional space. When we call add_constraint, |
| we are effectively taking an intersection with that constraint. */ |
| |
| class constraint_manager |
| { |
| public: |
| constraint_manager (region_model_manager *mgr) : m_mgr (mgr) {} |
| constraint_manager (const constraint_manager &other); |
| virtual ~constraint_manager () {} |
| |
| constraint_manager& operator= (const constraint_manager &other); |
| |
| hashval_t hash () const; |
| bool operator== (const constraint_manager &other) const; |
| bool operator!= (const constraint_manager &other) const |
| { |
| return !(*this == other); |
| } |
| |
| void print (pretty_printer *pp) const; |
| void dump_to_pp (pretty_printer *pp, bool multiline) const; |
| void dump (FILE *fp) const; |
| void dump () const; |
| |
| json::object *to_json () const; |
| |
| const equiv_class &get_equiv_class_by_index (unsigned idx) const |
| { |
| return *m_equiv_classes[idx]; |
| } |
| equiv_class &get_equiv_class_by_index (unsigned idx) |
| { |
| return *m_equiv_classes[idx]; |
| } |
| |
| equiv_class &get_equiv_class (const svalue *sval) |
| { |
| equiv_class_id ec_id = get_or_add_equiv_class (sval); |
| return ec_id.get_obj (*this); |
| } |
| |
| bool add_constraint (const svalue *lhs, |
| enum tree_code op, |
| const svalue *rhs); |
| |
| bool add_constraint (equiv_class_id lhs_ec_id, |
| enum tree_code op, |
| equiv_class_id rhs_ec_id); |
| |
| void add_unknown_constraint (equiv_class_id lhs_ec_id, |
| enum tree_code op, |
| equiv_class_id rhs_ec_id); |
| |
| bool add_bounded_ranges (const svalue *sval, |
| const bounded_ranges *ranges); |
| |
| bool get_equiv_class_by_svalue (const svalue *sval, |
| equiv_class_id *out) const; |
| equiv_class_id get_or_add_equiv_class (const svalue *sval); |
| tristate eval_condition (equiv_class_id lhs, |
| enum tree_code op, |
| equiv_class_id rhs) const; |
| tristate eval_condition (equiv_class_id lhs_ec, |
| enum tree_code op, |
| tree rhs_const) const; |
| tristate eval_condition (const svalue *lhs, |
| enum tree_code op, |
| const svalue *rhs) const; |
| range get_ec_bounds (equiv_class_id ec_id) const; |
| |
| /* PurgeCriteria should have: |
| bool should_purge_p (const svalue *sval) const. */ |
| template <typename PurgeCriteria> |
| void purge (const PurgeCriteria &p, purge_stats *stats); |
| |
| void on_liveness_change (const svalue_set &live_svalues, |
| const region_model *model); |
| void purge_state_involving (const svalue *sval); |
| |
| void canonicalize (); |
| |
| static void merge (const constraint_manager &cm_a, |
| const constraint_manager &cm_b, |
| constraint_manager *out); |
| |
| void for_each_fact (fact_visitor *) const; |
| |
| void validate () const; |
| |
| bounded_ranges_manager *get_range_manager () const; |
| |
| auto_delete_vec<equiv_class> m_equiv_classes; |
| auto_vec<constraint> m_constraints; |
| auto_vec<bounded_ranges_constraint> m_bounded_ranges_constraints; |
| |
| private: |
| void add_constraint_internal (equiv_class_id lhs_id, |
| enum constraint_op c_op, |
| equiv_class_id rhs_id); |
| |
| region_model_manager *m_mgr; |
| }; |
| |
| } // namespace ana |
| |
| #endif /* GCC_ANALYZER_CONSTRAINT_MANAGER_H */ |