diff mbox series

[commited,gcc13] ipa: Compare jump functions in ICF (PR 113907)

Message ID ri6r0e4beya.fsf@virgil.suse.cz
State New
Headers show
Series [commited,gcc13] ipa: Compare jump functions in ICF (PR 113907) | expand

Commit Message

Martin Jambor May 14, 2024, 3:17 p.m. UTC
Hi,

This is a manual backport of r14-9840-g1162861439fd3c from master.
Manual because the bits and value range representation in jump
functions have changes during the gcc 14 development cycle.

In PR 113907 comment #58, Honza found a case where ICF thinks bodies
of functions are equivalent but becaise of difference in aliases in a
memory access, different aggregate jump functions are associated with
supposedly equivalent call statements.  This patch adds a way to
compare jump functions and plugs it into ICF to avoid the issue.

Bootstrapped and tested on x86_64-linux.  Committed to the gcc-13
branch.

Martin


gcc/ChangeLog:

2024-05-14  Martin Jambor  <mjambor@suse.cz>

	PR ipa/113907
	* ipa-prop.h (ipa_jump_functions_equivalent_p): Declare.
	(values_equal_for_ipcp_p): Likewise.
	* ipa-prop.cc (ipa_agg_pass_through_jf_equivalent_p): New function.
	(ipa_agg_jump_functions_equivalent_p): Likewise.
	(ipa_jump_functions_equivalent_p): Likewise.
	* ipa-cp.cc (values_equal_for_ipcp_p): Make function public.
	* ipa-icf-gimple.cc: Include alloc-pool.h, symbol-summary.h, sreal.h,
	ipa-cp.h and ipa-prop.h.
	(func_checker::compare_gimple_call): Comapre jump functions.

gcc/testsuite/ChangeLog:

2024-05-10  Martin Jambor  <mjambor@suse.cz>

	PR ipa/113907
	* gcc.dg/lto/pr113907_0.c: New.
	* gcc.dg/lto/pr113907_1.c: Likewise.
	* gcc.dg/lto/pr113907_2.c: Likewise.
---
 gcc/ipa-cp.cc                         |   2 +-
 gcc/ipa-icf-gimple.cc                 |  29 +++++
 gcc/ipa-prop.cc                       | 157 ++++++++++++++++++++++++++
 gcc/ipa-prop.h                        |   3 +
 gcc/testsuite/gcc.dg/lto/pr113907_0.c |  18 +++
 gcc/testsuite/gcc.dg/lto/pr113907_1.c |  35 ++++++
 gcc/testsuite/gcc.dg/lto/pr113907_2.c |  11 ++
 7 files changed, 254 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gcc.dg/lto/pr113907_0.c
 create mode 100644 gcc/testsuite/gcc.dg/lto/pr113907_1.c
 create mode 100644 gcc/testsuite/gcc.dg/lto/pr113907_2.c
diff mbox series

Patch

diff --git a/gcc/ipa-cp.cc b/gcc/ipa-cp.cc
index b3e0f62e400..8f36608cf33 100644
--- a/gcc/ipa-cp.cc
+++ b/gcc/ipa-cp.cc
@@ -458,7 +458,7 @@  ipcp_lattice<valtype>::is_single_const ()
 
 /* Return true iff X and Y should be considered equal values by IPA-CP.  */
 
-static bool
+bool
 values_equal_for_ipcp_p (tree x, tree y)
 {
   gcc_checking_assert (x != NULL_TREE && y != NULL_TREE);
diff --git a/gcc/ipa-icf-gimple.cc b/gcc/ipa-icf-gimple.cc
index 49302ad56c6..054a557bd58 100644
--- a/gcc/ipa-icf-gimple.cc
+++ b/gcc/ipa-icf-gimple.cc
@@ -42,7 +42,11 @@  along with GCC; see the file COPYING3.  If not see
 #include "tree-sra.h"
 
 #include "tree-ssa-alias-compare.h"
+#include "alloc-pool.h"
+#include "symbol-summary.h"
 #include "ipa-icf-gimple.h"
+#include "sreal.h"
+#include "ipa-prop.h"
 
 namespace ipa_icf_gimple {
 
@@ -751,6 +755,31 @@  func_checker::compare_gimple_call (gcall *s1, gcall *s2)
       && !compatible_types_p (TREE_TYPE (t1), TREE_TYPE (t2)))
     return return_false_with_msg ("GIMPLE internal call LHS type mismatch");
 
+  if (!gimple_call_internal_p (s1))
+    {
+      cgraph_edge *e1 = cgraph_node::get (m_source_func_decl)->get_edge (s1);
+      cgraph_edge *e2 = cgraph_node::get (m_target_func_decl)->get_edge (s2);
+      class ipa_edge_args *args1 = ipa_edge_args_sum->get (e1);
+      class ipa_edge_args *args2 = ipa_edge_args_sum->get (e2);
+      if ((args1 != nullptr) != (args2 != nullptr))
+	return return_false_with_msg ("ipa_edge_args mismatch");
+      if (args1)
+	{
+	  int n1 = ipa_get_cs_argument_count (args1);
+	  int n2 = ipa_get_cs_argument_count (args2);
+	  if (n1 != n2)
+	    return return_false_with_msg ("ipa_edge_args nargs mismatch");
+	  for (int i = 0; i < n1; i++)
+	    {
+	      struct ipa_jump_func *jf1 = ipa_get_ith_jump_func (args1, i);
+	      struct ipa_jump_func *jf2 = ipa_get_ith_jump_func (args2, i);
+	      if (((jf1 != nullptr) != (jf2 != nullptr))
+		  || (jf1 && !ipa_jump_functions_equivalent_p (jf1, jf2)))
+		return return_false_with_msg ("jump function mismatch");
+	    }
+	}
+    }
+
   return compare_operand (t1, t2, get_operand_access_type (&map, t1));
 }
 
diff --git a/gcc/ipa-prop.cc b/gcc/ipa-prop.cc
index 0d816749534..11ba2521b2c 100644
--- a/gcc/ipa-prop.cc
+++ b/gcc/ipa-prop.cc
@@ -6022,5 +6022,162 @@  ipcp_transform_function (struct cgraph_node *node)
   return modified_mem_access ? TODO_update_ssa_only_virtuals : 0;
 }
 
+/* Return true if the two pass_through components of two jump functions are
+   known to be equivalent.  AGG_JF denotes whether they are part of aggregate
+   functions or not.  The function can be used before the IPA phase of IPA-CP
+   or inlining because it cannot cope with refdesc changes these passes can
+   carry out.  */
+
+static bool
+ipa_agg_pass_through_jf_equivalent_p (ipa_pass_through_data *ipt1,
+				      ipa_pass_through_data *ipt2,
+				      bool agg_jf)
+
+{
+  gcc_assert (agg_jf ||
+	      (!ipt1->refdesc_decremented && !ipt2->refdesc_decremented));
+  if (ipt1->operation != ipt2->operation
+      || ipt1->formal_id != ipt2->formal_id
+      || (!agg_jf && (ipt1->agg_preserved != ipt2->agg_preserved)))
+    return false;
+  if (((ipt1->operand != NULL_TREE) != (ipt2->operand != NULL_TREE))
+      || (ipt1->operand
+	  && !values_equal_for_ipcp_p (ipt1->operand, ipt2->operand)))
+    return false;
+  return true;
+}
+
+/* Return true if the two aggregate jump functions are known to be equivalent.
+   The function can be used before the IPA phase of IPA-CP or inlining because
+   it cannot cope with refdesc changes these passes can carry out.  */
+
+static bool
+ipa_agg_jump_functions_equivalent_p (ipa_agg_jf_item *ajf1,
+				     ipa_agg_jf_item *ajf2)
+{
+  if (ajf1->offset != ajf2->offset
+      || ajf1->jftype != ajf2->jftype
+      || !types_compatible_p (ajf1->type, ajf2->type))
+    return false;
+
+  switch (ajf1->jftype)
+    {
+    case IPA_JF_CONST:
+      if (!values_equal_for_ipcp_p (ajf1->value.constant,
+				    ajf2->value.constant))
+	return false;
+      break;
+    case IPA_JF_PASS_THROUGH:
+      {
+	ipa_pass_through_data *ipt1 = &ajf1->value.pass_through;
+	ipa_pass_through_data *ipt2 = &ajf2->value.pass_through;
+	if (!ipa_agg_pass_through_jf_equivalent_p (ipt1, ipt2, true))
+	  return false;
+      }
+      break;
+    case IPA_JF_LOAD_AGG:
+      {
+	ipa_load_agg_data *ila1 = &ajf1->value.load_agg;
+	ipa_load_agg_data *ila2 = &ajf2->value.load_agg;
+	if (!ipa_agg_pass_through_jf_equivalent_p (&ila1->pass_through,
+						   &ila2->pass_through, true))
+	  return false;
+	if (ila1->offset != ila2->offset
+	    || ila1->by_ref != ila2->by_ref
+	    || !types_compatible_p (ila1->type, ila2->type))
+	  return false;
+      }
+      break;
+    default:
+	gcc_unreachable ();
+    }
+  return true;
+}
+
+/* Return true if the two jump functions are known to be equivalent.  The
+   function can be used before the IPA phase of IPA-CP or inlining because it
+   cannot cope with refdesc changes these passes can carry out.  */
+
+bool
+ipa_jump_functions_equivalent_p (ipa_jump_func *jf1, ipa_jump_func *jf2)
+{
+  if (jf1->type != jf2->type)
+    return false;
+
+  switch (jf1->type)
+    {
+    case IPA_JF_UNKNOWN:
+      break;
+    case IPA_JF_CONST:
+      {
+	tree cst1 = ipa_get_jf_constant (jf1);
+	tree cst2 = ipa_get_jf_constant (jf2);
+	if (!values_equal_for_ipcp_p (cst1, cst2))
+	  return false;
+
+	ipa_cst_ref_desc *rd1 = jfunc_rdesc_usable (jf1);
+	ipa_cst_ref_desc *rd2 = jfunc_rdesc_usable (jf2);
+	if (rd1 && rd2)
+	  {
+	    gcc_assert (rd1->refcount == 1
+			&& rd2->refcount == 1);
+	    gcc_assert (!rd1->next_duplicate && !rd2->next_duplicate);
+	  }
+	else if (rd1)
+	  return false;
+	else if (rd2)
+	  return false;
+      }
+      break;
+    case IPA_JF_PASS_THROUGH:
+      {
+	ipa_pass_through_data *ipt1 = &jf1->value.pass_through;
+	ipa_pass_through_data *ipt2 = &jf2->value.pass_through;
+	if (!ipa_agg_pass_through_jf_equivalent_p (ipt1, ipt2, false))
+	  return false;
+      }
+      break;
+    case IPA_JF_ANCESTOR:
+      {
+	ipa_ancestor_jf_data *ia1 = &jf1->value.ancestor;
+	ipa_ancestor_jf_data *ia2 = &jf2->value.ancestor;
+
+	if (ia1->formal_id != ia2->formal_id
+	    || ia1->agg_preserved != ia2->agg_preserved
+	    || ia1->keep_null != ia2->keep_null
+	    || ia1->offset != ia2->offset)
+	  return false;
+      }
+      break;
+    default:
+      gcc_unreachable ();
+    }
+
+  if (((jf1->bits != nullptr) != (jf2->bits != nullptr))
+      || (jf1->bits && ((jf1->bits->value != jf2->bits->value)
+			|| (jf1->bits->mask != jf2->bits->mask))))
+    return false;
+
+  if (((jf1->m_vr != nullptr) != (jf2->m_vr != nullptr))
+      || (jf1->m_vr && *jf1->m_vr != *jf2->m_vr))
+    return false;
+
+  unsigned alen = vec_safe_length (jf1->agg.items);
+  if (vec_safe_length (jf2->agg.items) != alen)
+    return false;
+
+  if (!alen)
+    return true;
+
+  if (jf1->agg.by_ref != jf2->agg.by_ref)
+    return false;
+
+  for (unsigned i = 0 ; i < alen; i++)
+    if (!ipa_agg_jump_functions_equivalent_p (&(*jf1->agg.items)[i],
+					      &(*jf2->agg.items)[i]))
+      return false;
+
+  return true;
+}
 
 #include "gt-ipa-prop.h"
diff --git a/gcc/ipa-prop.h b/gcc/ipa-prop.h
index 7eb5c8f44ea..fdea86228d8 100644
--- a/gcc/ipa-prop.h
+++ b/gcc/ipa-prop.h
@@ -1194,11 +1194,14 @@  bool ipcp_get_parm_bits (tree, tree *, widest_int *);
 bool unadjusted_ptr_and_unit_offset (tree op, tree *ret,
 				     poly_int64 *offset_ret);
 
+bool ipa_jump_functions_equivalent_p (ipa_jump_func *jf1, ipa_jump_func *jf2);
+
 /* From tree-sra.cc:  */
 tree build_ref_for_offset (location_t, tree, poly_int64, bool, tree,
 			   gimple_stmt_iterator *, bool);
 
 /* In ipa-cp.cc  */
 void ipa_cp_cc_finalize (void);
+bool values_equal_for_ipcp_p (tree x, tree y);
 
 #endif /* IPA_PROP_H */
diff --git a/gcc/testsuite/gcc.dg/lto/pr113907_0.c b/gcc/testsuite/gcc.dg/lto/pr113907_0.c
new file mode 100644
index 00000000000..3c4dd475c01
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lto/pr113907_0.c
@@ -0,0 +1,18 @@ 
+/* { dg-lto-do run } */
+/* { dg-lto-options {{-O3 -flto}} }  */
+
+struct bar {int a;};
+struct foo {int a;};
+struct barp {struct bar *f; struct bar *g;};
+extern struct foo **ptr;
+int test2 (void *);
+int test3 (void *);
+int
+testb(void)
+{
+  struct bar *fp;
+  test2 ((void *)&fp);
+  fp = (void *) 0;
+  (*ptr)++;
+  test3 ((void *)&fp);
+}
diff --git a/gcc/testsuite/gcc.dg/lto/pr113907_1.c b/gcc/testsuite/gcc.dg/lto/pr113907_1.c
new file mode 100644
index 00000000000..1c48bfd83a4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lto/pr113907_1.c
@@ -0,0 +1,35 @@ 
+__attribute__((used)) int val,val2 = 1;
+
+struct foo {int a;};
+
+struct foo **ptr;
+
+__attribute__ ((noipa))
+int
+test2 (void *a)
+{
+  ptr = (struct foo **)a;
+}
+int test3 (void *a);
+
+int
+test(void)
+{
+  struct foo *fp;
+  test2 ((void *)&fp);
+  fp = (void *) 0;
+  (*ptr)++;
+  test3 ((void *)&fp);
+}
+
+int testb (void);
+
+int
+main()
+{
+  for (int i = 0; i < val2; i++)
+  if (val)
+    testb ();
+  else
+    test();
+}
diff --git a/gcc/testsuite/gcc.dg/lto/pr113907_2.c b/gcc/testsuite/gcc.dg/lto/pr113907_2.c
new file mode 100644
index 00000000000..172e86e8b0b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lto/pr113907_2.c
@@ -0,0 +1,11 @@ 
+/* { dg-options "-O3 -flto -fno-strict-aliasing" }  */
+
+__attribute__ ((noinline))
+int
+test3 (void *a)
+{
+  if (!*(void **)a)
+          __builtin_abort ();
+  return 0;
+}
+