| 1 | /* Tracking equivalence classes and constraints at a point on an execution path. |
| 2 | Copyright (C) 2019-2025 Free Software Foundation, Inc. |
| 3 | Contributed by David Malcolm <dmalcolm@redhat.com>. |
| 4 | |
| 5 | This file is part of GCC. |
| 6 | |
| 7 | GCC is free software; you can redistribute it and/or modify it |
| 8 | under the terms of the GNU General Public License as published by |
| 9 | the Free Software Foundation; either version 3, or (at your option) |
| 10 | any later version. |
| 11 | |
| 12 | GCC is distributed in the hope that it will be useful, but |
| 13 | WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 15 | General Public License for more details. |
| 16 | |
| 17 | You should have received a copy of the GNU General Public License |
| 18 | along with GCC; see the file COPYING3. If not see |
| 19 | <http://www.gnu.org/licenses/>. */ |
| 20 | |
| 21 | #ifndef GCC_ANALYZER_CONSTRAINT_MANAGER_H |
| 22 | #define GCC_ANALYZER_CONSTRAINT_MANAGER_H |
| 23 | |
| 24 | namespace ana { |
| 25 | |
| 26 | class constraint_manager; |
| 27 | |
| 28 | enum class bound_kind |
| 29 | { |
| 30 | lower, |
| 31 | upper |
| 32 | }; |
| 33 | |
| 34 | /* One of the end-points of a range. */ |
| 35 | |
| 36 | struct bound |
| 37 | { |
| 38 | bound () : m_constant (NULL_TREE), m_closed (false) {} |
| 39 | bound (tree constant, bool closed) |
| 40 | : m_constant (constant), m_closed (closed) {} |
| 41 | |
| 42 | void ensure_closed (enum bound_kind bound_kind); |
| 43 | |
| 44 | const char * get_relation_as_str () const; |
| 45 | |
| 46 | tree m_constant; |
| 47 | bool m_closed; |
| 48 | }; |
| 49 | |
| 50 | /* A range of values, used for determining if a value has been |
| 51 | constrained to just one possible constant value. */ |
| 52 | |
| 53 | class range |
| 54 | { |
| 55 | public: |
| 56 | range () : m_lower_bound (), m_upper_bound () {} |
| 57 | range (const bound &lower, const bound &upper) |
| 58 | : m_lower_bound (lower), m_upper_bound (upper) {} |
| 59 | |
| 60 | void dump_to_pp (pretty_printer *pp) const; |
| 61 | void dump () const; |
| 62 | |
| 63 | tree constrained_to_single_element (); |
| 64 | |
| 65 | tristate eval_condition (enum tree_code op, |
| 66 | tree rhs_const) const; |
| 67 | bool below_lower_bound (tree rhs_const) const; |
| 68 | bool above_upper_bound (tree rhs_const) const; |
| 69 | |
| 70 | bool add_bound (bound b, enum bound_kind bound_kind); |
| 71 | bool add_bound (enum tree_code op, tree rhs_const); |
| 72 | |
| 73 | private: |
| 74 | bound m_lower_bound; |
| 75 | bound m_upper_bound; |
| 76 | }; |
| 77 | |
| 78 | /* A closed range of values with constant integer bounds |
| 79 | e.g. [3, 5] for the set {3, 4, 5}. */ |
| 80 | |
| 81 | struct bounded_range |
| 82 | { |
| 83 | bounded_range (const_tree lower, const_tree upper); |
| 84 | |
| 85 | void dump_to_pp (pretty_printer *pp, bool show_types) const; |
| 86 | void dump (bool show_types) const; |
| 87 | |
| 88 | std::unique_ptr<json::object> to_json () const; |
| 89 | |
| 90 | std::unique_ptr<text_art::widget> |
| 91 | make_dump_widget (const text_art::dump_widget_info &dwi) const; |
| 92 | |
| 93 | bool contains_p (tree cst) const; |
| 94 | |
| 95 | bool intersects_p (const bounded_range &other, |
| 96 | bounded_range *out) const; |
| 97 | |
| 98 | bool operator== (const bounded_range &other) const; |
| 99 | bool operator!= (const bounded_range &other) const |
| 100 | { |
| 101 | return !(*this == other); |
| 102 | } |
| 103 | |
| 104 | static int cmp (const bounded_range &a, const bounded_range &b); |
| 105 | |
| 106 | bool singleton_p () const |
| 107 | { |
| 108 | return tree_int_cst_equal (m_lower, m_upper); |
| 109 | } |
| 110 | |
| 111 | tree m_lower; |
| 112 | tree m_upper; |
| 113 | |
| 114 | private: |
| 115 | static void set_json_attr (json::object &obj, const char *name, tree value); |
| 116 | }; |
| 117 | |
| 118 | /* A collection of bounded_range instances, suitable |
| 119 | for representing the ranges on a case label within a switch |
| 120 | statement. */ |
| 121 | |
| 122 | struct bounded_ranges |
| 123 | { |
| 124 | public: |
| 125 | typedef bounded_ranges key_t; |
| 126 | |
| 127 | bounded_ranges (const bounded_range &range); |
| 128 | bounded_ranges (const vec<bounded_range> &ranges); |
| 129 | bounded_ranges (enum tree_code op, tree rhs_const); |
| 130 | |
| 131 | bool operator== (const bounded_ranges &other) const; |
| 132 | |
| 133 | hashval_t get_hash () const { return m_hash; } |
| 134 | |
| 135 | void dump_to_pp (pretty_printer *pp, bool show_types) const; |
| 136 | void dump (bool show_types) const; |
| 137 | |
| 138 | std::unique_ptr<json::value> to_json () const; |
| 139 | |
| 140 | void add_to_dump_widget (text_art::tree_widget &parent, |
| 141 | const text_art::dump_widget_info &dwi) const; |
| 142 | |
| 143 | tristate eval_condition (enum tree_code op, |
| 144 | tree rhs_const, |
| 145 | bounded_ranges_manager *mgr) const; |
| 146 | |
| 147 | bool contain_p (tree cst) const; |
| 148 | bool empty_p () const { return m_ranges.length () == 0; } |
| 149 | |
| 150 | static int cmp (const bounded_ranges *a, const bounded_ranges *b); |
| 151 | |
| 152 | unsigned get_count () const { return m_ranges.length (); } |
| 153 | const bounded_range &get_range (unsigned idx) const { return m_ranges[idx]; } |
| 154 | |
| 155 | private: |
| 156 | void canonicalize (); |
| 157 | void validate () const; |
| 158 | |
| 159 | friend class bounded_ranges_manager; |
| 160 | |
| 161 | auto_vec<bounded_range> m_ranges; |
| 162 | hashval_t m_hash; |
| 163 | }; |
| 164 | |
| 165 | } // namespace ana |
| 166 | |
| 167 | template <> struct default_hash_traits<bounded_ranges::key_t> |
| 168 | : public member_function_hash_traits<bounded_ranges::key_t> |
| 169 | { |
| 170 | static const bool empty_zero_p = true; |
| 171 | }; |
| 172 | |
| 173 | namespace ana { |
| 174 | |
| 175 | /* An object to own and consolidate bounded_ranges instances. |
| 176 | This also caches the mapping from switch_cfg_superedge |
| 177 | bounded_ranges instances, so that get_or_create_ranges_for_switch is |
| 178 | memoized. */ |
| 179 | |
| 180 | class bounded_ranges_manager |
| 181 | { |
| 182 | public: |
| 183 | ~bounded_ranges_manager (); |
| 184 | |
| 185 | const bounded_ranges * |
| 186 | get_or_create_ranges_for_switch (const switch_cfg_superedge *edge, |
| 187 | const gswitch *switch_stmt); |
| 188 | |
| 189 | const bounded_ranges *get_or_create_empty (); |
| 190 | const bounded_ranges *get_or_create_point (const_tree value); |
| 191 | const bounded_ranges *get_or_create_range (const_tree lower_bound, |
| 192 | const_tree upper_bound); |
| 193 | const bounded_ranges * |
| 194 | get_or_create_union (const vec <const bounded_ranges *> &others); |
| 195 | const bounded_ranges * |
| 196 | get_or_create_intersection (const bounded_ranges *a, |
| 197 | const bounded_ranges *b); |
| 198 | const bounded_ranges * |
| 199 | get_or_create_inverse (const bounded_ranges *other, tree type); |
| 200 | |
| 201 | void log_stats (logger *logger, bool show_objs) const; |
| 202 | |
| 203 | private: |
| 204 | const bounded_ranges * |
| 205 | create_ranges_for_switch (const switch_cfg_superedge &edge, |
| 206 | const gswitch *switch_stmt); |
| 207 | |
| 208 | const bounded_ranges * |
| 209 | make_case_label_ranges (const gswitch *switch_stmt, |
| 210 | tree case_label); |
| 211 | |
| 212 | const bounded_ranges *consolidate (bounded_ranges *); |
| 213 | |
| 214 | struct hash_traits_t : public typed_noop_remove<bounded_ranges *> |
| 215 | { |
| 216 | typedef bounded_ranges *key_type; |
| 217 | typedef bounded_ranges *value_type; |
| 218 | |
| 219 | static inline bool |
| 220 | equal (const key_type &k1, const key_type &k2) |
| 221 | { |
| 222 | return *k1 == *k2; |
| 223 | } |
| 224 | static inline hashval_t |
| 225 | hash (const key_type &k) |
| 226 | { |
| 227 | return k->get_hash (); |
| 228 | } |
| 229 | static inline bool is_empty (key_type k) { return k == NULL; } |
| 230 | static inline void mark_empty (key_type &k) { k = NULL; } |
| 231 | static inline bool is_deleted (key_type k) |
| 232 | { |
| 233 | return k == reinterpret_cast<key_type> (1); |
| 234 | } |
| 235 | |
| 236 | static const bool empty_zero_p = true; |
| 237 | }; |
| 238 | struct traits_t : public simple_hashmap_traits<hash_traits_t, |
| 239 | bounded_ranges *> |
| 240 | { |
| 241 | }; |
| 242 | typedef hash_map<bounded_ranges *, bounded_ranges *, traits_t> map_t; |
| 243 | map_t m_map; |
| 244 | |
| 245 | typedef hash_map<const switch_cfg_superedge *, |
| 246 | const bounded_ranges *> edge_cache_t; |
| 247 | edge_cache_t m_edge_cache; |
| 248 | }; |
| 249 | |
| 250 | /* An equivalence class within a constraint manager: a set of |
| 251 | svalues that are known to all be equal to each other, |
| 252 | together with an optional tree constant that they are equal to. */ |
| 253 | |
| 254 | class equiv_class |
| 255 | { |
| 256 | public: |
| 257 | equiv_class (); |
| 258 | equiv_class (const equiv_class &other); |
| 259 | |
| 260 | hashval_t hash () const; |
| 261 | bool operator== (const equiv_class &other); |
| 262 | |
| 263 | void add (const svalue *sval); |
| 264 | bool del (const svalue *sval); |
| 265 | |
| 266 | tree get_any_constant () const { return m_constant; } |
| 267 | |
| 268 | const svalue *get_representative () const; |
| 269 | |
| 270 | void canonicalize (); |
| 271 | |
| 272 | void print (pretty_printer *pp) const; |
| 273 | |
| 274 | std::unique_ptr<json::object> to_json () const; |
| 275 | |
| 276 | std::unique_ptr<text_art::tree_widget> |
| 277 | make_dump_widget (const text_art::dump_widget_info &dwi, |
| 278 | unsigned id) const; |
| 279 | |
| 280 | bool contains_non_constant_p () const; |
| 281 | |
| 282 | /* An equivalence class can contain multiple constants (e.g. multiple |
| 283 | different zeroes, for different types); these are just for the last |
| 284 | constant added. */ |
| 285 | tree m_constant; |
| 286 | const svalue *m_cst_sval; |
| 287 | |
| 288 | // TODO: should this be a set rather than a vec? |
| 289 | auto_vec<const svalue *> m_vars; |
| 290 | }; |
| 291 | |
| 292 | /* The various kinds of constraint. */ |
| 293 | |
| 294 | enum constraint_op |
| 295 | { |
| 296 | CONSTRAINT_NE, |
| 297 | CONSTRAINT_LT, |
| 298 | CONSTRAINT_LE |
| 299 | }; |
| 300 | |
| 301 | const char *constraint_op_code (enum constraint_op c_op); |
| 302 | |
| 303 | /* An ID for an equiv_class within a constraint_manager. Internally, this |
| 304 | is an index into a vector of equiv_class * within the constraint_manager. */ |
| 305 | |
| 306 | class equiv_class_id |
| 307 | { |
| 308 | public: |
| 309 | static equiv_class_id null () { return equiv_class_id (-1); } |
| 310 | |
| 311 | equiv_class_id (unsigned idx) : m_idx (idx) {} |
| 312 | const equiv_class &get_obj (const constraint_manager &cm) const; |
| 313 | equiv_class &get_obj (constraint_manager &cm) const; |
| 314 | |
| 315 | bool operator== (const equiv_class_id &other) const |
| 316 | { |
| 317 | return m_idx == other.m_idx; |
| 318 | } |
| 319 | bool operator!= (const equiv_class_id &other) const |
| 320 | { |
| 321 | return m_idx != other.m_idx; |
| 322 | } |
| 323 | |
| 324 | bool null_p () const { return m_idx == -1; } |
| 325 | |
| 326 | static equiv_class_id from_int (int idx) { return equiv_class_id (idx); } |
| 327 | int as_int () const { return m_idx; } |
| 328 | |
| 329 | void print (pretty_printer *pp) const; |
| 330 | |
| 331 | void update_for_removal (equiv_class_id other) |
| 332 | { |
| 333 | if (m_idx > other.m_idx) |
| 334 | m_idx--; |
| 335 | } |
| 336 | |
| 337 | int m_idx; |
| 338 | }; |
| 339 | |
| 340 | /* A relationship between two equivalence classes in a constraint_manager. */ |
| 341 | |
| 342 | class constraint |
| 343 | { |
| 344 | public: |
| 345 | constraint (equiv_class_id lhs, enum constraint_op c_op, equiv_class_id rhs) |
| 346 | : m_lhs (lhs), m_op (c_op), m_rhs (rhs) |
| 347 | { |
| 348 | gcc_assert (!lhs.null_p ()); |
| 349 | gcc_assert (!rhs.null_p ()); |
| 350 | } |
| 351 | |
| 352 | void print (pretty_printer *pp, const constraint_manager &cm) const; |
| 353 | |
| 354 | std::unique_ptr<json::object> to_json () const; |
| 355 | |
| 356 | std::unique_ptr<text_art::widget> |
| 357 | make_dump_widget (const text_art::dump_widget_info &dwi, |
| 358 | const constraint_manager &cm) const; |
| 359 | |
| 360 | hashval_t hash () const; |
| 361 | bool operator== (const constraint &other) const; |
| 362 | |
| 363 | /* Is this an ordering, rather than a "!=". */ |
| 364 | bool is_ordering_p () const |
| 365 | { |
| 366 | return m_op != CONSTRAINT_NE; |
| 367 | } |
| 368 | |
| 369 | bool implied_by (const constraint &other, |
| 370 | const constraint_manager &cm) const; |
| 371 | |
| 372 | equiv_class_id m_lhs; |
| 373 | enum constraint_op m_op; |
| 374 | equiv_class_id m_rhs; |
| 375 | }; |
| 376 | |
| 377 | /* An abstract base class for use with constraint_manager::for_each_fact. */ |
| 378 | |
| 379 | class fact_visitor |
| 380 | { |
| 381 | public: |
| 382 | virtual ~fact_visitor () {} |
| 383 | virtual void on_fact (const svalue *lhs, |
| 384 | enum tree_code, |
| 385 | const svalue *rhs) = 0; |
| 386 | virtual void on_ranges (const svalue *lhs, |
| 387 | const bounded_ranges *ranges) = 0; |
| 388 | }; |
| 389 | |
| 390 | class bounded_ranges_constraint |
| 391 | { |
| 392 | public: |
| 393 | bounded_ranges_constraint (equiv_class_id ec_id, |
| 394 | const bounded_ranges *ranges) |
| 395 | : m_ec_id (ec_id), m_ranges (ranges) |
| 396 | { |
| 397 | } |
| 398 | |
| 399 | void print (pretty_printer *pp, const constraint_manager &cm) const; |
| 400 | |
| 401 | std::unique_ptr<json::object> to_json () const; |
| 402 | |
| 403 | bool operator== (const bounded_ranges_constraint &other) const; |
| 404 | bool operator!= (const bounded_ranges_constraint &other) const |
| 405 | { |
| 406 | return !(*this == other); |
| 407 | } |
| 408 | |
| 409 | void add_to_hash (inchash::hash *hstate) const; |
| 410 | |
| 411 | std::unique_ptr<text_art::tree_widget> |
| 412 | make_dump_widget (const text_art::dump_widget_info &dwi) const; |
| 413 | |
| 414 | equiv_class_id m_ec_id; |
| 415 | const bounded_ranges *m_ranges; |
| 416 | }; |
| 417 | |
| 418 | /* A collection of equivalence classes and constraints on them. |
| 419 | |
| 420 | Given N svalues, this can be thought of as representing a subset of |
| 421 | N-dimensional space. When we call add_constraint, |
| 422 | we are effectively taking an intersection with that constraint. */ |
| 423 | |
| 424 | class constraint_manager |
| 425 | { |
| 426 | public: |
| 427 | constraint_manager (region_model_manager *mgr) : m_mgr (mgr) {} |
| 428 | constraint_manager (const constraint_manager &other); |
| 429 | virtual ~constraint_manager () {} |
| 430 | |
| 431 | constraint_manager& operator= (const constraint_manager &other); |
| 432 | |
| 433 | hashval_t hash () const; |
| 434 | bool operator== (const constraint_manager &other) const; |
| 435 | bool operator!= (const constraint_manager &other) const |
| 436 | { |
| 437 | return !(*this == other); |
| 438 | } |
| 439 | |
| 440 | void print (pretty_printer *pp) const; |
| 441 | void dump_to_pp (pretty_printer *pp, bool multiline) const; |
| 442 | void dump (FILE *fp) const; |
| 443 | void dump () const; |
| 444 | |
| 445 | std::unique_ptr<json::object> to_json () const; |
| 446 | |
| 447 | std::unique_ptr<text_art::tree_widget> |
| 448 | make_dump_widget (const text_art::dump_widget_info &dwi) const; |
| 449 | |
| 450 | const equiv_class &get_equiv_class_by_index (unsigned idx) const |
| 451 | { |
| 452 | return *m_equiv_classes[idx]; |
| 453 | } |
| 454 | equiv_class &get_equiv_class_by_index (unsigned idx) |
| 455 | { |
| 456 | return *m_equiv_classes[idx]; |
| 457 | } |
| 458 | |
| 459 | equiv_class &get_equiv_class (const svalue *sval) |
| 460 | { |
| 461 | equiv_class_id ec_id = get_or_add_equiv_class (sval); |
| 462 | return ec_id.get_obj (cm&: *this); |
| 463 | } |
| 464 | |
| 465 | bool add_constraint (const svalue *lhs, |
| 466 | enum tree_code op, |
| 467 | const svalue *rhs); |
| 468 | |
| 469 | bool add_constraint (equiv_class_id lhs_ec_id, |
| 470 | enum tree_code op, |
| 471 | equiv_class_id rhs_ec_id); |
| 472 | |
| 473 | void add_unknown_constraint (equiv_class_id lhs_ec_id, |
| 474 | enum tree_code op, |
| 475 | equiv_class_id rhs_ec_id); |
| 476 | |
| 477 | bool add_bounded_ranges (const svalue *sval, |
| 478 | const bounded_ranges *ranges); |
| 479 | |
| 480 | bool get_equiv_class_by_svalue (const svalue *sval, |
| 481 | equiv_class_id *out) const; |
| 482 | bool sval_constrained_p (const svalue *sval) const; |
| 483 | equiv_class_id get_or_add_equiv_class (const svalue *sval); |
| 484 | tristate eval_condition (equiv_class_id lhs, |
| 485 | enum tree_code op, |
| 486 | equiv_class_id rhs) const; |
| 487 | tristate eval_condition (equiv_class_id lhs_ec, |
| 488 | enum tree_code op, |
| 489 | tree rhs_const) const; |
| 490 | tristate eval_condition (const svalue *lhs, |
| 491 | enum tree_code op, |
| 492 | const svalue *rhs) const; |
| 493 | range get_ec_bounds (equiv_class_id ec_id) const; |
| 494 | |
| 495 | /* PurgeCriteria should have: |
| 496 | bool should_purge_p (const svalue *sval) const. */ |
| 497 | template <typename PurgeCriteria> |
| 498 | void purge (const PurgeCriteria &p, purge_stats *stats); |
| 499 | |
| 500 | void on_liveness_change (const svalue_set &live_svalues, |
| 501 | const region_model *model); |
| 502 | void purge_state_involving (const svalue *sval); |
| 503 | |
| 504 | void canonicalize (); |
| 505 | |
| 506 | static void merge (const constraint_manager &cm_a, |
| 507 | const constraint_manager &cm_b, |
| 508 | constraint_manager *out); |
| 509 | |
| 510 | void for_each_fact (fact_visitor *) const; |
| 511 | |
| 512 | void validate () const; |
| 513 | |
| 514 | bounded_ranges_manager *get_range_manager () const; |
| 515 | |
| 516 | bool replay_call_summary (call_summary_replay &r, |
| 517 | const constraint_manager &summary); |
| 518 | |
| 519 | auto_delete_vec<equiv_class> m_equiv_classes; |
| 520 | auto_vec<constraint> m_constraints; |
| 521 | auto_vec<bounded_ranges_constraint> m_bounded_ranges_constraints; |
| 522 | |
| 523 | private: |
| 524 | void add_constraint_internal (equiv_class_id lhs_id, |
| 525 | enum constraint_op c_op, |
| 526 | equiv_class_id rhs_id); |
| 527 | bool impossible_derived_conditions_p (const svalue *lhs, |
| 528 | const svalue *rhs) const; |
| 529 | |
| 530 | region_model_manager *m_mgr; |
| 531 | }; |
| 532 | |
| 533 | } // namespace ana |
| 534 | |
| 535 | #endif /* GCC_ANALYZER_CONSTRAINT_MANAGER_H */ |
| 536 | |