On Sun, 8 Nov 2020, Jan Hubicka wrote:

> Hello,
> I would like to finally address the problem with ipa-icf being overly
> conservating on memory operand equality and having overly weak hash
> function. This leads to code size, compile time and compile time memory
> use regressions sompared to earlier versions where we merged more
> aggressively because of weaker TBAA.
> 
> Basically comparing memory operand should match for memory accesses that
> does same base+offset computations and have same behaviour WRT TBAA.
> When optimizing for size we would like to discard TBAA info in the case
> of TBAA mismatch if they perform same actual load.
> 
> From past discussion we conlcuded that operand_equal_p (in fold-const)
> is not good place to address this since it is used for generic trees as
> well.  The TBAA matching/hashing is best done in tree-ssa-alias since it
> is inherently tied to its internals.
> 
> I would like to do following
>  1) keep track what parameters are memory operands.  This is implemented
>     in the attached patch.
>  2) Add ao_ref interface for hasing and comparing references inheriting
>     fold-const.h:operand_compare which then can feed ICF equivalences
>     adding:
>     
>     int ao_ref_compare (ao_ref ref1, ao_ref ref2)
> 
>     It will return mask how refs differ:
>     
>     AO_REF_SEMANTICS if the actual access is different
>     (i.e. different base, offset or size of the access)
>     AO_REF_BASE_ALIAS_SET if base alias set differs.
>     AO_REF_ALIAS_SET if base ref set differs.
>     AO_REF_ACCESS_PATH if access path differs.
> 
>     and 
> 
>     ao_ref_hash (ao_ref hash, int flags)
> 
>     which will compute hashtable where flags are same AO_REF as above.
> 
>     Initial implementation can compare alias sets for !LTO and for LTO
>     compare TYPE_MAIN_VARIANTS.  We currently have no way to hash access
>     types for LTO, I plan to add that incremntally.
> 
>  3) Make ipa_icf use ao_ref_compare == 0 to do current merging and
>     !(ao_ref_compare & AO_REF_SEMANTICS) for size optimized code.
> 
>  4) Add way of hading alias sets for LTO.  This can be done by moving
>     canonical type hash out of lto that I think would be nice cleanup
>     anyway.  I have older patch for that (it needs to be untied from
>     TYPE_CANONICAL computation by adding extra hashtable but it has no
>     performance impact)
> 
>     Other alternative is to simply collect list of types relevant and
>     compare them at WPA time as done by earlier Martin's patch.
> 
> Later we could play with merging TBAA mismatches memory references with
> -O2+ and clearing them only for uninlined clones, but this is tricky and
> can wait for next stage1.
> 
> Does this make sense?

Well, guess we have to see if it works out and how ugly it will be.

Definitely not sth to rush for this stage1 ...

> Attached patch implements set 1.  I use walk_stmt_load_store_ops to
> discover memor operands by storing them to pointer set. This has the
> expense of extra hashing.  An alternative would be to embed the logic
> into icf walkers, but it seems to be fast enough and I do not see
> that as a good motivation to duplicate the logic into comparsion and
> hashers.
> 
> I have bootstrapped/regtstested on x86_64 and would like to commit it
> after some discussion tomorrow.
> 
> Honza
> 
> 
> 2020-11-08  Jan Hubicka  <hubi...@ucw.cz>
> 
>       * ipa-icf-gimple.c: Include gimple-walk.h.
>       (func_checker::compare_ssa_name): Update call of compare_operand.
>       (func_checker::hash_operand): Fix comment and add variant taking
>       operand_access_type parameter.
>       (func_checker::compare_operand): Add operand_access_type parameter.
>       (func_checker::compare_asm_inputs_outputs): Add
>       operand_access_type_map parameter; update use of
>       func_checker::compare_operand.
>       (func_checker::compare_gimple_call): Update use of
>       func_checker::compare_operand.
>       (func_checker::compare_gimple_assign): Likewise.
>       (func_checker::compare_gimple_cond): Likewise.
>       (func_checker::compare_gimple_switch): Likewise.
>       (func_checker::compare_gimple_return): Likewise.
>       (func_checker::compare_gimple_goto): Likewise.
>       (func_checker::compare_gimple_asm): Likewise.
>       (visit_load_store): New static functio.
>       (func_checker::classify_operands): New member function.
>       (func_checker::get_operand_access_type): New member function.
>       * ipa-icf-gimple.h (func_checker::operand_access_type): New enum
>       (func_checker::operand_access_type_map): New typedef.
>       (func_checker::compare_operand): Update prototype.
>       (func_checker::compare_asm_inputs_outputs): Likewise.
>       (func_checker::cleassify_operands): Declare.
>       (func_checker::get_operand_access_type): Declare.
>       (func_checker::hash_operand): New variant with operand_access_type.
>       * ipa-icf.c (sem_function::hash_stmt): Update uses of hash_operand.
>       (sem_function::compare_phi_node): Update use of compare_operand.
>       * tree-ssa-structalias.c (find_func_aliases_for_builtin_call):
> 
> diff --git a/gcc/ipa-icf-gimple.c b/gcc/ipa-icf-gimple.c
> index d5423a7e9b2..f75951f7c49 100644
> --- a/gcc/ipa-icf-gimple.c
> +++ b/gcc/ipa-icf-gimple.c
> @@ -38,6 +38,7 @@ along with GCC; see the file COPYING3.  If not see
>  #include "builtins.h"
>  #include "cfgloop.h"
>  #include "attribs.h"
> +#include "gimple-walk.h"
>  
>  #include "ipa-icf-gimple.h"
>  
> @@ -109,7 +110,7 @@ func_checker::compare_ssa_name (const_tree t1, const_tree 
> t2)
>        tree b1 = SSA_NAME_VAR (t1);
>        tree b2 = SSA_NAME_VAR (t2);
>  
> -      return compare_operand (b1, b2);
> +      return compare_operand (b1, b2, OP_NORMAL);
>      }
>  
>    return true;
> @@ -212,8 +213,8 @@ func_checker::compatible_types_p (tree t1, tree t2)
>    return true;
>  }
>  
> -/* Function compare for equality given trees T1 and T2 which
> -   can be either a constant or a declaration type.  */
> +/* Add hash of ARG to HSTATE. FLAGS have same meaning
> +   as for operand_equal_p.  Works only if operand acces type is OP_NORMAL.  
> */
>  
>  void
>  func_checker::hash_operand (const_tree arg, inchash::hash &hstate,
> @@ -246,6 +247,16 @@ func_checker::hash_operand (const_tree arg, 
> inchash::hash &hstate,
>    return operand_compare::hash_operand (arg, hstate, flags);
>  }
>  
> +/* Add hash of ARG accesses according to ACCESS to HSTATE.
> +   FLAGS have same meaning as for operand_equal_p.  */
> +
> +void
> +func_checker::hash_operand (const_tree arg, inchash::hash &hstate,
> +                         unsigned int flags, operand_access_type)
> +{
> +  return hash_operand (arg, hstate, flags);
> +}
> +
>  bool
>  func_checker::operand_equal_p (const_tree t1, const_tree t2,
>                              unsigned int flags)
> @@ -291,12 +302,13 @@ func_checker::operand_equal_p (const_tree t1, 
> const_tree t2,
>    return operand_compare::operand_equal_p (t1, t2, flags);
>  }
>  
> -/* Function responsible for comparison of various operands T1 and T2.
> +/* Function responsible for comparison of various operands T1 and T2
> +   which are accessed as ACCESS.
>     If these components, from functions FUNC1 and FUNC2, are equal, true
>     is returned.  */
>  
>  bool
> -func_checker::compare_operand (tree t1, tree t2)
> +func_checker::compare_operand (tree t1, tree t2, operand_access_type access)
>  {
>    if (!t1 && !t2)
>      return true;
> @@ -304,11 +316,21 @@ func_checker::compare_operand (tree t1, tree t2)
>      return false;
>    if (operand_equal_p (t1, t2, OEP_MATCH_SIDE_EFFECTS))
>      return true;
> -  return return_false_with_msg ("operand_equal_p failed");
> +  switch (access)
> +    {
> +    case OP_MEMORY:
> +      return return_false_with_msg
> +              ("operand_equal_p failed (access == memory)");
> +    case OP_NORMAL:
> +      return return_false_with_msg
> +              ("operand_equal_p failed (access == normal)");
> +    }
> +  gcc_unreachable ();
>  }
>  
>  bool
> -func_checker::compare_asm_inputs_outputs (tree t1, tree t2)
> +func_checker::compare_asm_inputs_outputs (tree t1, tree t2,
> +                                       operand_access_type_map *map)
>  {
>    gcc_assert (TREE_CODE (t1) == TREE_LIST);
>    gcc_assert (TREE_CODE (t2) == TREE_LIST);
> @@ -318,7 +340,8 @@ func_checker::compare_asm_inputs_outputs (tree t1, tree 
> t2)
>        if (!t2)
>       return false;
>  
> -      if (!compare_operand (TREE_VALUE (t1), TREE_VALUE (t2)))
> +      if (!compare_operand (TREE_VALUE (t1), TREE_VALUE (t2),
> +                         get_operand_access_type (map, t1)))
>       return return_false ();
>  
>        tree p1 = TREE_PURPOSE (t1);
> @@ -545,9 +568,12 @@ func_checker::compare_gimple_call (gcall *s1, gcall *s2)
>    if (gimple_call_num_args (s1) != gimple_call_num_args (s2))
>      return false;
>  
> +  operand_access_type_map map (5);
> +  classify_operands (s1, &map);
> +
>    t1 = gimple_call_fn (s1);
>    t2 = gimple_call_fn (s2);
> -  if (!compare_operand (t1, t2))
> +  if (!compare_operand (t1, t2, get_operand_access_type (&map, t1)))
>      return return_false ();
>  
>    /* Compare flags.  */
> @@ -579,7 +605,8 @@ func_checker::compare_gimple_call (gcall *s1, gcall *s2)
>    tree chain2 = gimple_call_chain (s2);
>    if ((chain1 && !chain2)
>        || (!chain1 && chain2)
> -      || !compare_operand (chain1, chain2))
> +      || !compare_operand (chain1, chain2,
> +                        get_operand_access_type (&map, chain1)))
>      return return_false_with_msg ("static call chains are different");
>  
>    /* Checking of argument.  */
> @@ -588,7 +615,7 @@ func_checker::compare_gimple_call (gcall *s1, gcall *s2)
>        t1 = gimple_call_arg (s1, i);
>        t2 = gimple_call_arg (s2, i);
>  
> -      if (!compare_operand (t1, t2))
> +      if (!compare_operand (t1, t2, get_operand_access_type (&map, t1)))
>       return return_false_with_msg ("GIMPLE call operands are different");
>      }
>  
> @@ -596,7 +623,7 @@ func_checker::compare_gimple_call (gcall *s1, gcall *s2)
>    t1 = gimple_get_lhs (s1);
>    t2 = gimple_get_lhs (s2);
>  
> -  return compare_operand (t1, t2);
> +  return compare_operand (t1, t2, get_operand_access_type (&map, t1));
>  }
>  
>  
> @@ -622,6 +649,9 @@ func_checker::compare_gimple_assign (gimple *s1, gimple 
> *s2)
>    if (code1 != code2)
>      return false;
>  
> +  operand_access_type_map map (5);
> +  classify_operands (s1, &map);
> +
>    for (i = 0; i < gimple_num_ops (s1); i++)
>      {
>        arg1 = gimple_op (s1, i);
> @@ -634,7 +664,7 @@ func_checker::compare_gimple_assign (gimple *s1, gimple 
> *s2)
>           return return_false_with_msg ("GIMPLE NOP LHS type mismatch");
>       }
>  
> -      if (!compare_operand (arg1, arg2))
> +      if (!compare_operand (arg1, arg2, get_operand_access_type (&map, 
> arg1)))
>       return return_false_with_msg ("GIMPLE assignment operands "
>                                     "are different");
>      }
> @@ -661,13 +691,13 @@ func_checker::compare_gimple_cond (gimple *s1, gimple 
> *s2)
>    t1 = gimple_cond_lhs (s1);
>    t2 = gimple_cond_lhs (s2);
>  
> -  if (!compare_operand (t1, t2))
> +  if (!compare_operand (t1, t2, OP_NORMAL))
>      return false;
>  
>    t1 = gimple_cond_rhs (s1);
>    t2 = gimple_cond_rhs (s2);
>  
> -  return compare_operand (t1, t2);
> +  return compare_operand (t1, t2, OP_NORMAL);
>  }
>  
>  /* Verifies for given GIMPLE_LABEL stmts S1 and S2 that
> @@ -706,7 +736,7 @@ func_checker::compare_gimple_switch (const gswitch *g1, 
> const gswitch *g2)
>    tree t1 = gimple_switch_index (g1);
>    tree t2 = gimple_switch_index (g2);
>  
> -  if (!compare_operand (t1, t2))
> +  if (!compare_operand (t1, t2, OP_NORMAL))
>      return false;
>  
>    for (i = 0; i < lsize1; i++)
> @@ -733,7 +763,7 @@ func_checker::compare_gimple_switch (const gswitch *g1, 
> const gswitch *g2)
>         label1 = CASE_LABEL (label1);
>         label2 = CASE_LABEL (label2);
>  
> -       if (!compare_operand (label1, label2))
> +       if (!compare_operand (label1, label2, OP_NORMAL))
>           return return_false_with_msg ("switch label_exprs are different");
>       }
>        else if (!tree_int_cst_equal (label1, label2))
> @@ -758,7 +788,10 @@ func_checker::compare_gimple_return (const greturn *g1, 
> const greturn *g2)
>    if (t1 == NULL && t2 == NULL)
>      return true;
>    else
> -    return compare_operand (t1, t2);
> +    {
> +      operand_access_type_map map (3);
> +      return compare_operand (t1, t2, get_operand_access_type (&map, t1));
> +    }
>  }
>  
>  /* Verifies for given GIMPLEs S1 and S2 that
> @@ -775,7 +808,7 @@ func_checker::compare_gimple_goto (gimple *g1, gimple *g2)
>    if (TREE_CODE (dest1) != TREE_CODE (dest2) || TREE_CODE (dest1) != 
> SSA_NAME)
>      return false;
>  
> -  return compare_operand (dest1, dest2);
> +  return compare_operand (dest1, dest2, OP_NORMAL);
>  }
>  
>  /* Verifies for given GIMPLE_RESX stmts S1 and S2 that
> @@ -819,12 +852,15 @@ func_checker::compare_gimple_asm (const gasm *g1, const 
> gasm *g2)
>    if (strcmp (gimple_asm_string (g1), gimple_asm_string (g2)) != 0)
>      return return_false_with_msg ("ASM strings are different");
>  
> +  operand_access_type_map map (5);
> +  classify_operands (g1, &map);
> +
>    for (unsigned i = 0; i < gimple_asm_ninputs (g1); i++)
>      {
>        tree input1 = gimple_asm_input_op (g1, i);
>        tree input2 = gimple_asm_input_op (g2, i);
>  
> -      if (!compare_asm_inputs_outputs (input1, input2))
> +      if (!compare_asm_inputs_outputs (input1, input2, &map))
>       return return_false_with_msg ("ASM input is different");
>      }
>  
> @@ -833,7 +869,7 @@ func_checker::compare_gimple_asm (const gasm *g1, const 
> gasm *g2)
>        tree output1 = gimple_asm_output_op (g1, i);
>        tree output2 = gimple_asm_output_op (g2, i);
>  
> -      if (!compare_asm_inputs_outputs (output1, output2))
> +      if (!compare_asm_inputs_outputs (output1, output2, &map))
>       return return_false_with_msg ("ASM output is different");
>      }
>  
> @@ -850,4 +886,35 @@ func_checker::compare_gimple_asm (const gasm *g1, const 
> gasm *g2)
>    return true;
>  }
>  
> +/* Helper for func_checker::classify_operands.  Record that T is a load.  */
> +
> +static bool
> +visit_load_store (gimple *, tree t, tree, void *data)
> +{
> +  func_checker::operand_access_type_map *map =
> +    (func_checker::operand_access_type_map *) data;
> +  map->add (t);
> +  return false;
> +}
> +
> +/* Compute hash map determining access types of operands.  */
> +
> +void
> +func_checker::classify_operands (const gimple *stmt,
> +                              operand_access_type_map *map)
> +{
> +  walk_stmt_load_store_ops (const_cast <gimple *> (stmt),
> +                         (void *)map, visit_load_store, visit_load_store);
> +}
> +
> +/* Return access type of a given operand.  */
> +
> +func_checker::operand_access_type
> +func_checker::get_operand_access_type (operand_access_type_map *map, tree t)
> +{
> +  if (map->contains (t))
> +    return OP_MEMORY;
> +  return OP_NORMAL;
> +}
> +
>  } // ipa_icf_gimple namespace
> diff --git a/gcc/ipa-icf-gimple.h b/gcc/ipa-icf-gimple.h
> index 28459d217ea..ed2da18d5b7 100644
> --- a/gcc/ipa-icf-gimple.h
> +++ b/gcc/ipa-icf-gimple.h
> @@ -200,14 +200,19 @@ public:
>    /* Verification function for declaration trees T1 and T2.  */
>    bool compare_decl (const_tree t1, const_tree t2);
>  
> +  /* Compute hash map MAP that determines loads and stores of STMT.  */
> +  enum operand_access_type {OP_MEMORY, OP_NORMAL};
> +  typedef hash_set<tree> operand_access_type_map;
> +
>    /* Function responsible for comparison of various operands T1 and T2.
>       If these components, from functions FUNC1 and FUNC2, are equal, true
>       is returned.  */
> -  bool compare_operand (tree t1, tree t2);
> +  bool compare_operand (tree t1, tree t2, operand_access_type type);
>  
>    /* Compares GIMPLE ASM inputs (or outputs) where we iterate tree chain
>       and compare both TREE_PURPOSEs and TREE_VALUEs.  */
> -  bool compare_asm_inputs_outputs (tree t1, tree t2);
> +  bool compare_asm_inputs_outputs (tree t1, tree t2,
> +                                operand_access_type_map *map);
>  
>    /* Verifies that trees T1 and T2, representing function declarations
>       are equivalent from perspective of ICF.  */
> @@ -230,7 +235,13 @@ public:
>       first parameter of a function.  */
>    static bool compatible_types_p (tree t1, tree t2);
>  
> +  /* Compute hash map determining access types of operands.  */
> +  static void classify_operands (const gimple *stmt,
> +                              operand_access_type_map *map);
>  
> +  /* Return access type of a given operand.  */
> +  static operand_access_type get_operand_access_type
> +              (operand_access_type_map *map, tree);
>  private:
>    /* Vector mapping source SSA names to target ones.  */
>    vec <int> m_source_ssa_names;
> @@ -272,6 +283,8 @@ public:
>    /* Generate a hash value for an expression.  This can be used iteratively
>       by passing a previous result as the HSTATE argument.  */
>    virtual void hash_operand (const_tree, inchash::hash &, unsigned flags);
> +  void hash_operand (const_tree, inchash::hash &, unsigned flags,
> +                          operand_access_type access);
>  };
>  
>  } // ipa_icf_gimple namespace
> diff --git a/gcc/ipa-icf.c b/gcc/ipa-icf.c
> index 8cae076b3ce..83f9786b4b2 100644
> --- a/gcc/ipa-icf.c
> +++ b/gcc/ipa-icf.c
> @@ -1419,33 +1419,32 @@ sem_function::hash_stmt (gimple *stmt, inchash::hash 
> &hstate)
>      {
>      case GIMPLE_SWITCH:
>        m_checker->hash_operand (gimple_switch_index (as_a <gswitch *> (stmt)),
> -                          hstate, 0);
> +                            hstate, 0, func_checker::OP_NORMAL);
>        break;
>      case GIMPLE_ASSIGN:
>        hstate.add_int (gimple_assign_rhs_code (stmt));
> -      if (commutative_tree_code (gimple_assign_rhs_code (stmt))
> -       || commutative_ternary_tree_code (gimple_assign_rhs_code (stmt)))
> -     {
> -       m_checker->hash_operand (gimple_assign_rhs1 (stmt), hstate, 0);
> -       m_checker->hash_operand (gimple_assign_rhs2 (stmt), hstate, 0);
> -       if (commutative_ternary_tree_code (gimple_assign_rhs_code (stmt)))
> -         m_checker->hash_operand (gimple_assign_rhs3 (stmt), hstate, 0);
> -       m_checker->hash_operand (gimple_assign_lhs (stmt), hstate, 0);
> -     }
>        /* fall through */
>      case GIMPLE_CALL:
>      case GIMPLE_ASM:
>      case GIMPLE_COND:
>      case GIMPLE_GOTO:
>      case GIMPLE_RETURN:
> -      /* All these statements are equivalent if their operands are.  */
> -      for (unsigned i = 0; i < gimple_num_ops (stmt); ++i)
> -     m_checker->hash_operand (gimple_op (stmt, i), hstate, 0);
> -      /* Consider nocf_check attribute in hash as it affects code
> -      generation.  */
> -      if (code == GIMPLE_CALL
> -       && flag_cf_protection & CF_BRANCH)
> -     hstate.add_flag (gimple_call_nocf_check_p (as_a <gcall *> (stmt)));
> +      {
> +     func_checker::operand_access_type_map map (5);
> +     func_checker::classify_operands (stmt, &map);
> +
> +     /* All these statements are equivalent if their operands are.  */
> +     for (unsigned i = 0; i < gimple_num_ops (stmt); ++i)
> +       m_checker->hash_operand (gimple_op (stmt, i), hstate, 0,
> +                                func_checker::get_operand_access_type
> +                                     (&map, gimple_op (stmt, i)));
> +     /* Consider nocf_check attribute in hash as it affects code
> +        generation.  */
> +     if (code == GIMPLE_CALL
> +         && flag_cf_protection & CF_BRANCH)
> +       hstate.add_flag (gimple_call_nocf_check_p (as_a <gcall *> (stmt)));
> +      }
> +      break;
>      default:
>        break;
>      }
> @@ -1534,7 +1533,8 @@ sem_function::compare_phi_node (basic_block bb1, 
> basic_block bb2)
>        tree phi_result1 = gimple_phi_result (phi1);
>        tree phi_result2 = gimple_phi_result (phi2);
>  
> -      if (!m_checker->compare_operand (phi_result1, phi_result2))
> +      if (!m_checker->compare_operand (phi_result1, phi_result2,
> +                                    func_checker::OP_NORMAL))
>       return return_false_with_msg ("PHI results are different");
>  
>        size1 = gimple_phi_num_args (phi1);
> @@ -1548,7 +1548,7 @@ sem_function::compare_phi_node (basic_block bb1, 
> basic_block bb2)
>         t1 = gimple_phi_arg (phi1, i)->def;
>         t2 = gimple_phi_arg (phi2, i)->def;
>  
> -       if (!m_checker->compare_operand (t1, t2))
> +       if (!m_checker->compare_operand (t1, t2, func_checker::OP_NORMAL))
>           return return_false ();
>  
>         e1 = gimple_phi_arg_edge (phi1, i);
> 

-- 
Richard Biener <rguent...@suse.de>
SUSE Software Solutions Germany GmbH, Maxfeldstrasse 5, 90409 Nuernberg,
Germany; GF: Felix Imend

Reply via email to