| /* IPA predicates. | |
| Copyright (C) 2003-2026 Free Software Foundation, Inc. | |
| Contributed by Jan Hubicka | |
| 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/>. */ | |
| #include "config.h" | |
| #include "system.h" | |
| #include "coretypes.h" | |
| #include "backend.h" | |
| #include "tree.h" | |
| #include "cgraph.h" | |
| #include "tree-vrp.h" | |
| #include "alloc-pool.h" | |
| #include "symbol-summary.h" | |
| #include "sreal.h" | |
| #include "ipa-cp.h" | |
| #include "ipa-prop.h" | |
| #include "ipa-fnsummary.h" | |
| #include "real.h" | |
| #include "fold-const.h" | |
| #include "tree-pretty-print.h" | |
| #include "gimple.h" | |
| #include "gimplify.h" | |
| #include "data-streamer.h" | |
| /* Check whether two set of operations have same effects. */ | |
| static bool | |
| expr_eval_ops_equal_p (expr_eval_ops ops1, expr_eval_ops ops2) | |
| { | |
| if (ops1) | |
| { | |
| if (!ops2 || ops1->length () != ops2->length ()) | |
| return false; | |
| for (unsigned i = 0; i < ops1->length (); i++) | |
| { | |
| expr_eval_op &op1 = (*ops1)[i]; | |
| expr_eval_op &op2 = (*ops2)[i]; | |
| if (op1.code != op2.code | |
| || op1.index != op2.index | |
| || !vrp_operand_equal_p (op1.val[0], op2.val[0]) | |
| || !vrp_operand_equal_p (op1.val[1], op2.val[1]) | |
| || !types_compatible_p (op1.type, op2.type)) | |
| return false; | |
| } | |
| return true; | |
| } | |
| return !ops2; | |
| } | |
| /* Add clause CLAUSE into the predicate P. | |
| When CONDITIONS is NULL do not perform checking whether NEW_CLAUSE | |
| is obviously true. This is useful only when NEW_CLAUSE is known to be | |
| sane. */ | |
| void | |
| ipa_predicate::add_clause (conditions conditions, clause_t new_clause) | |
| { | |
| int i; | |
| int i2; | |
| int insert_here = -1; | |
| int c1, c2; | |
| /* True clause. */ | |
| if (!new_clause) | |
| return; | |
| /* False clause makes the whole predicate false. Kill the other variants. */ | |
| if (new_clause == (1 << ipa_predicate::false_condition)) | |
| { | |
| *this = false; | |
| return; | |
| } | |
| if (*this == false) | |
| return; | |
| /* No one should be silly enough to add false into nontrivial clauses. */ | |
| gcc_checking_assert (!(new_clause & (1 << ipa_predicate::false_condition))); | |
| /* Look where to insert the new_clause. At the same time prune out | |
| new_clauses of P that are implied by the new new_clause and thus | |
| redundant. */ | |
| for (i = 0, i2 = 0; i <= max_clauses; i++) | |
| { | |
| m_clause[i2] = m_clause[i]; | |
| if (!m_clause[i]) | |
| break; | |
| /* If m_clause[i] implies new_clause, there is nothing to add. */ | |
| if ((m_clause[i] & new_clause) == m_clause[i]) | |
| { | |
| /* We had nothing to add, none of clauses should've become | |
| redundant. */ | |
| gcc_checking_assert (i == i2); | |
| return; | |
| } | |
| if (m_clause[i] < new_clause && insert_here < 0) | |
| insert_here = i2; | |
| /* If new_clause implies clause[i], then clause[i] becomes redundant. | |
| Otherwise the clause[i] has to stay. */ | |
| if ((m_clause[i] & new_clause) != new_clause) | |
| i2++; | |
| } | |
| /* Look for clauses that are obviously true. I.e. | |
| op0 == 5 || op0 != 5. */ | |
| if (conditions) | |
| for (c1 = ipa_predicate::first_dynamic_condition; | |
| c1 < num_conditions; c1++) | |
| { | |
| condition *cc1; | |
| if (!(new_clause & (1 << c1))) | |
| continue; | |
| cc1 = &(*conditions)[c1 - ipa_predicate::first_dynamic_condition]; | |
| /* We have no way to represent !changed and !is_not_constant | |
| and thus there is no point for looking for them. */ | |
| if (cc1->code == changed || cc1->code == is_not_constant || cc1->code == not_sra_candidate) | |
| continue; | |
| for (c2 = c1 + 1; c2 < num_conditions; c2++) | |
| if (new_clause & (1 << c2)) | |
| { | |
| condition *cc2 = | |
| &(*conditions)[c2 - ipa_predicate::first_dynamic_condition]; | |
| if (cc1->operand_num == cc2->operand_num | |
| && vrp_operand_equal_p (cc1->val, cc2->val) | |
| && cc2->code != is_not_constant | |
| && cc2->code != not_sra_candidate | |
| && cc2->code != changed | |
| && expr_eval_ops_equal_p (cc1->param_ops, cc2->param_ops) | |
| && cc2->agg_contents == cc1->agg_contents | |
| && cc2->by_ref == cc1->by_ref | |
| && types_compatible_p (cc2->type, cc1->type) | |
| && cc1->code == invert_tree_comparison (cc2->code, | |
| HONOR_NANS (cc1->val))) | |
| return; | |
| } | |
| } | |
| /* We run out of variants. Be conservative in positive direction. */ | |
| if (i2 == max_clauses) | |
| return; | |
| /* Keep clauses in decreasing order. This makes equivalence testing easy. */ | |
| m_clause[i2 + 1] = 0; | |
| if (insert_here >= 0) | |
| for (; i2 > insert_here; i2--) | |
| m_clause[i2] = m_clause[i2 - 1]; | |
| else | |
| insert_here = i2; | |
| m_clause[insert_here] = new_clause; | |
| } | |
| /* Do THIS &= P. */ | |
| ipa_predicate & | |
| ipa_predicate::operator &= (const ipa_predicate &p) | |
| { | |
| /* Avoid busy work. */ | |
| if (p == false || *this == true) | |
| { | |
| *this = p; | |
| return *this; | |
| } | |
| if (*this == false || p == true || this == &p) | |
| return *this; | |
| int i; | |
| /* See how far ipa_predicates match. */ | |
| for (i = 0; m_clause[i] && m_clause[i] == p.m_clause[i]; i++) | |
| { | |
| gcc_checking_assert (i < max_clauses); | |
| } | |
| /* Combine the ipa_predicates rest. */ | |
| for (; p.m_clause[i]; i++) | |
| { | |
| gcc_checking_assert (i < max_clauses); | |
| add_clause (NULL, p.m_clause[i]); | |
| } | |
| return *this; | |
| } | |
| /* Return THIS | P2. */ | |
| ipa_predicate | |
| ipa_predicate::or_with (conditions conditions, | |
| const ipa_predicate &p) const | |
| { | |
| /* Avoid busy work. */ | |
| if (p == false || *this == true || *this == p) | |
| return *this; | |
| if (*this == false || p == true) | |
| return p; | |
| /* OK, combine the predicates. */ | |
| ipa_predicate out = true; | |
| for (int i = 0; m_clause[i]; i++) | |
| for (int j = 0; p.m_clause[j]; j++) | |
| { | |
| gcc_checking_assert (i < max_clauses && j < max_clauses); | |
| out.add_clause (conditions, m_clause[i] | p.m_clause[j]); | |
| } | |
| return out; | |
| } | |
| /* Having partial truth assignment in POSSIBLE_TRUTHS, return false | |
| if predicate P is known to be false. */ | |
| bool | |
| ipa_predicate::evaluate (clause_t possible_truths) const | |
| { | |
| int i; | |
| /* True remains true. */ | |
| if (*this == true) | |
| return true; | |
| gcc_assert (!(possible_truths & (1 << ipa_predicate::false_condition))); | |
| /* See if we can find clause we can disprove. */ | |
| for (i = 0; m_clause[i]; i++) | |
| { | |
| gcc_checking_assert (i < max_clauses); | |
| if (!(m_clause[i] & possible_truths)) | |
| return false; | |
| } | |
| return true; | |
| } | |
| /* Return the probability in range 0...REG_BR_PROB_BASE that the predicated | |
| instruction will be recomputed per invocation of the inlined call. */ | |
| int | |
| ipa_predicate::probability (conditions conds, | |
| clause_t possible_truths, | |
| vec<inline_param_summary> inline_param_summary) const | |
| { | |
| int i; | |
| int combined_prob = REG_BR_PROB_BASE; | |
| /* True remains true. */ | |
| if (*this == true) | |
| return REG_BR_PROB_BASE; | |
| if (*this == false) | |
| return 0; | |
| gcc_assert (!(possible_truths & (1 << ipa_predicate::false_condition))); | |
| /* See if we can find clause we can disprove. */ | |
| for (i = 0; m_clause[i]; i++) | |
| { | |
| gcc_checking_assert (i < max_clauses); | |
| if (!(m_clause[i] & possible_truths)) | |
| return 0; | |
| else | |
| { | |
| int this_prob = 0; | |
| int i2; | |
| if (!inline_param_summary.exists ()) | |
| return REG_BR_PROB_BASE; | |
| for (i2 = 0; i2 < num_conditions; i2++) | |
| if ((m_clause[i] & possible_truths) & (1 << i2)) | |
| { | |
| if (i2 >= ipa_predicate::first_dynamic_condition) | |
| { | |
| condition *c = | |
| &(*conds)[i2 - ipa_predicate::first_dynamic_condition]; | |
| if (c->code == ipa_predicate::changed | |
| && (c->operand_num < | |
| (int) inline_param_summary.length ())) | |
| { | |
| int iprob = | |
| inline_param_summary[c->operand_num].change_prob; | |
| this_prob = MAX (this_prob, iprob); | |
| } | |
| else | |
| this_prob = REG_BR_PROB_BASE; | |
| } | |
| else | |
| this_prob = REG_BR_PROB_BASE; | |
| } | |
| combined_prob = MIN (this_prob, combined_prob); | |
| if (!combined_prob) | |
| return 0; | |
| } | |
| } | |
| return combined_prob; | |
| } | |
| /* Dump conditional COND. */ | |
| void | |
| dump_condition (FILE *f, conditions conditions, int cond) | |
| { | |
| condition *c; | |
| if (cond == ipa_predicate::false_condition) | |
| fprintf (f, "false"); | |
| else if (cond == ipa_predicate::not_inlined_condition) | |
| fprintf (f, "not inlined"); | |
| else | |
| { | |
| c = &(*conditions)[cond - ipa_predicate::first_dynamic_condition]; | |
| fprintf (f, "op%i", c->operand_num); | |
| if (c->agg_contents) | |
| fprintf (f, "[%soffset: " HOST_WIDE_INT_PRINT_DEC "]", | |
| c->by_ref ? "ref " : "", c->offset); | |
| for (unsigned i = 0; i < vec_safe_length (c->param_ops); i++) | |
| { | |
| expr_eval_op &op = (*(c->param_ops))[i]; | |
| const char *op_name = op_symbol_code (op.code); | |
| if (op_name == op_symbol_code (ERROR_MARK)) | |
| op_name = get_tree_code_name (op.code); | |
| fprintf (f, ",("); | |
| if (!op.val[0]) | |
| { | |
| switch (op.code) | |
| { | |
| case FLOAT_EXPR: | |
| case FIX_TRUNC_EXPR: | |
| case FIXED_CONVERT_EXPR: | |
| case VIEW_CONVERT_EXPR: | |
| CASE_CONVERT: | |
| if (op.code == VIEW_CONVERT_EXPR) | |
| fprintf (f, "VCE"); | |
| fprintf (f, "("); | |
| print_generic_expr (f, op.type); | |
| fprintf (f, ")" ); | |
| break; | |
| default: | |
| fprintf (f, "%s", op_name); | |
| } | |
| fprintf (f, " #"); | |
| } | |
| else if (!op.val[1]) | |
| { | |
| if (op.index) | |
| { | |
| print_generic_expr (f, op.val[0]); | |
| fprintf (f, " %s #", op_name); | |
| } | |
| else | |
| { | |
| fprintf (f, "# %s ", op_name); | |
| print_generic_expr (f, op.val[0]); | |
| } | |
| } | |
| else | |
| { | |
| fprintf (f, "%s ", op_name); | |
| switch (op.index) | |
| { | |
| case 0: | |
| fprintf (f, "#, "); | |
| print_generic_expr (f, op.val[0]); | |
| fprintf (f, ", "); | |
| print_generic_expr (f, op.val[1]); | |
| break; | |
| case 1: | |
| print_generic_expr (f, op.val[0]); | |
| fprintf (f, ", #, "); | |
| print_generic_expr (f, op.val[1]); | |
| break; | |
| case 2: | |
| print_generic_expr (f, op.val[0]); | |
| fprintf (f, ", "); | |
| print_generic_expr (f, op.val[1]); | |
| fprintf (f, ", #"); | |
| break; | |
| default: | |
| fprintf (f, "*, *, *"); | |
| } | |
| } | |
| fprintf (f, ")"); | |
| } | |
| if (c->code == ipa_predicate::is_not_constant) | |
| { | |
| fprintf (f, " not constant"); | |
| return; | |
| } | |
| if (c->code == ipa_predicate::changed) | |
| { | |
| fprintf (f, " changed"); | |
| return; | |
| } | |
| if (c->code == ipa_predicate::not_sra_candidate) | |
| { | |
| fprintf (f, " not sra candidate"); | |
| return; | |
| } | |
| fprintf (f, " %s ", op_symbol_code (c->code)); | |
| print_generic_expr (f, c->val); | |
| } | |
| } | |
| /* Dump clause CLAUSE. */ | |
| static void | |
| dump_clause (FILE *f, conditions conds, clause_t clause) | |
| { | |
| int i; | |
| bool found = false; | |
| fprintf (f, "("); | |
| if (!clause) | |
| fprintf (f, "true"); | |
| for (i = 0; i < ipa_predicate::num_conditions; i++) | |
| if (clause & (1 << i)) | |
| { | |
| if (found) | |
| fprintf (f, " || "); | |
| found = true; | |
| dump_condition (f, conds, i); | |
| } | |
| fprintf (f, ")"); | |
| } | |
| /* Dump THIS to F. CONDS a vector of conditions used when evaluating | |
| ipa_predicates. When NL is true new line is output at the end of dump. */ | |
| void | |
| ipa_predicate::dump (FILE *f, conditions conds, bool nl) const | |
| { | |
| int i; | |
| if (*this == true) | |
| dump_clause (f, conds, 0); | |
| else | |
| for (i = 0; m_clause[i]; i++) | |
| { | |
| if (i) | |
| fprintf (f, " && "); | |
| dump_clause (f, conds, m_clause[i]); | |
| } | |
| if (nl) | |
| fprintf (f, "\n"); | |
| } | |
| void | |
| ipa_predicate::debug (conditions conds) const | |
| { | |
| dump (stderr, conds); | |
| } | |
| /* Remap predicate THIS of former function to be predicate of duplicated function. | |
| POSSIBLE_TRUTHS is clause of possible truths in the duplicated node, | |
| INFO is inline summary of the duplicated node. */ | |
| ipa_predicate | |
| ipa_predicate::remap_after_duplication (clause_t possible_truths) | |
| { | |
| int j; | |
| ipa_predicate out = true; | |
| for (j = 0; m_clause[j]; j++) | |
| if (!(possible_truths & m_clause[j])) | |
| return false; | |
| else | |
| out.add_clause (NULL, possible_truths & m_clause[j]); | |
| return out; | |
| } | |
| /* Translate all conditions from callee representation into caller | |
| representation and symbolically evaluate predicate THIS into new predicate. | |
| INFO is ipa_fn_summary of function we are adding predicate into, CALLEE_INFO | |
| is summary of function predicate P is from. OPERAND_MAP is array giving | |
| callee formal IDs the caller formal IDs. POSSSIBLE_TRUTHS is clause of all | |
| callee conditions that may be true in caller context. TOPLEV_PREDICATE is | |
| predicate under which callee is executed. OFFSET_MAP is an array of | |
| offsets that need to be added to conditions, negative offset means that | |
| conditions relying on values passed by reference have to be discarded | |
| because they might not be preserved (and should be considered offset zero | |
| for other purposes). */ | |
| ipa_predicate | |
| ipa_predicate::remap_after_inlining (class ipa_fn_summary *info, | |
| class ipa_node_params *params_summary, | |
| class ipa_fn_summary *callee_info, | |
| const vec<int> &operand_map, | |
| const vec<HOST_WIDE_INT> &offset_map, | |
| clause_t possible_truths, | |
| const ipa_predicate &toplev_predicate) | |
| { | |
| int i; | |
| ipa_predicate out = true; | |
| /* True ipa_predicate is easy. */ | |
| if (*this == true) | |
| return toplev_predicate; | |
| for (i = 0; m_clause[i]; i++) | |
| { | |
| clause_t clause = m_clause[i]; | |
| int cond; | |
| ipa_predicate clause_predicate = false; | |
| gcc_assert (i < max_clauses); | |
| for (cond = 0; cond < num_conditions; cond++) | |
| /* Do we have condition we can't disprove? */ | |
| if (clause & possible_truths & (1 << cond)) | |
| { | |
| ipa_predicate cond_predicate; | |
| /* Work out if the condition can translate to predicate in the | |
| inlined function. */ | |
| if (cond >= ipa_predicate::first_dynamic_condition) | |
| { | |
| struct condition *c; | |
| int index = cond - ipa_predicate::first_dynamic_condition; | |
| c = &(*callee_info->conds)[index]; | |
| /* See if we can remap condition operand to caller's operand. | |
| Otherwise give up. */ | |
| if (!operand_map.exists () | |
| || (int) operand_map.length () <= c->operand_num | |
| || operand_map[c->operand_num] == -1 | |
| /* TODO: For non-aggregate conditions, adding an offset is | |
| basically an arithmetic jump function processing which | |
| we should support in future. */ | |
| || ((!c->agg_contents || !c->by_ref) | |
| && offset_map[c->operand_num] > 0) | |
| || (c->agg_contents && c->by_ref | |
| && offset_map[c->operand_num] < 0)) | |
| cond_predicate = true; | |
| else | |
| { | |
| struct agg_position_info ap; | |
| HOST_WIDE_INT offset_delta = offset_map[c->operand_num]; | |
| if (offset_delta < 0) | |
| { | |
| gcc_checking_assert (!c->agg_contents || !c->by_ref); | |
| offset_delta = 0; | |
| } | |
| gcc_assert (!c->agg_contents | |
| || c->by_ref || offset_delta == 0); | |
| ap.offset = c->offset + offset_delta; | |
| ap.agg_contents = c->agg_contents; | |
| ap.by_ref = c->by_ref; | |
| cond_predicate = add_condition (info, params_summary, | |
| operand_map[c->operand_num], | |
| c->type, &ap, c->code, | |
| c->val, c->param_ops); | |
| } | |
| } | |
| /* Fixed conditions remains same, construct single | |
| condition predicate. */ | |
| else | |
| cond_predicate = ipa_predicate::predicate_testing_cond (cond); | |
| clause_predicate = clause_predicate.or_with (info->conds, | |
| cond_predicate); | |
| } | |
| out &= clause_predicate; | |
| } | |
| out &= toplev_predicate; | |
| return out; | |
| } | |
| /* Read predicate from IB. */ | |
| void | |
| ipa_predicate::stream_in (class lto_input_block *ib) | |
| { | |
| clause_t clause; | |
| int k = 0; | |
| do | |
| { | |
| gcc_assert (k <= max_clauses); | |
| clause = m_clause[k++] = streamer_read_uhwi (ib); | |
| } | |
| while (clause); | |
| /* Zero-initialize the remaining clauses in OUT. */ | |
| while (k <= max_clauses) | |
| m_clause[k++] = 0; | |
| } | |
| /* Write predicate P to OB. */ | |
| void | |
| ipa_predicate::stream_out (struct output_block *ob) | |
| { | |
| int j; | |
| for (j = 0; m_clause[j]; j++) | |
| { | |
| gcc_assert (j < max_clauses); | |
| streamer_write_uhwi (ob, m_clause[j]); | |
| } | |
| streamer_write_uhwi (ob, 0); | |
| } | |
| /* Add condition to condition list SUMMARY. OPERAND_NUM, TYPE, CODE, VAL and | |
| PARAM_OPS correspond to fields of condition structure. AGGPOS describes | |
| whether the used operand is loaded from an aggregate and where in the | |
| aggregate it is. It can be NULL, which means this not a load from an | |
| aggregate. */ | |
| ipa_predicate | |
| add_condition (class ipa_fn_summary *summary, | |
| class ipa_node_params *params_summary, | |
| int operand_num, | |
| tree type, struct agg_position_info *aggpos, | |
| enum tree_code code, tree val, expr_eval_ops param_ops) | |
| { | |
| int i, j; | |
| struct condition *c; | |
| struct condition new_cond; | |
| HOST_WIDE_INT offset; | |
| bool agg_contents, by_ref; | |
| expr_eval_op *op; | |
| if (params_summary) | |
| ipa_set_param_used_by_ipa_predicates (params_summary, operand_num, true); | |
| if (aggpos) | |
| { | |
| offset = aggpos->offset; | |
| agg_contents = aggpos->agg_contents; | |
| by_ref = aggpos->by_ref; | |
| } | |
| else | |
| { | |
| offset = 0; | |
| agg_contents = false; | |
| by_ref = false; | |
| } | |
| gcc_checking_assert (operand_num >= 0); | |
| for (i = 0; vec_safe_iterate (summary->conds, i, &c); i++) | |
| { | |
| if (c->operand_num == operand_num | |
| && c->code == code | |
| && types_compatible_p (c->type, type) | |
| && vrp_operand_equal_p (c->val, val) | |
| && c->agg_contents == agg_contents | |
| && expr_eval_ops_equal_p (c->param_ops, param_ops) | |
| && (!agg_contents || (c->offset == offset && c->by_ref == by_ref))) | |
| return ipa_predicate::predicate_testing_cond (i); | |
| } | |
| /* Too many conditions. Give up and return constant true. */ | |
| if (i == ipa_predicate::num_conditions - ipa_predicate::first_dynamic_condition) | |
| return true; | |
| new_cond.operand_num = operand_num; | |
| new_cond.code = code; | |
| new_cond.type = unshare_expr_without_location (type); | |
| new_cond.val = val ? unshare_expr_without_location (val) : val; | |
| new_cond.agg_contents = agg_contents; | |
| new_cond.by_ref = by_ref; | |
| new_cond.offset = offset; | |
| new_cond.param_ops = vec_safe_copy (param_ops); | |
| for (j = 0; vec_safe_iterate (new_cond.param_ops, j, &op); j++) | |
| { | |
| if (op->val[0]) | |
| op->val[0] = unshare_expr_without_location (op->val[0]); | |
| if (op->val[1]) | |
| op->val[1] = unshare_expr_without_location (op->val[1]); | |
| } | |
| vec_safe_push (summary->conds, new_cond); | |
| return ipa_predicate::predicate_testing_cond (i); | |
| } |