Hi,

On Tue, Nov 01, 2011 at 10:37:10AM +0100, Richard Guenther wrote:
> On Mon, Oct 31, 2011 at 5:58 PM, Martin Jambor <mjam...@suse.cz> wrote:
> > On Fri, Oct 28, 2011 at 11:21:23AM +0200, Richard Guenther wrote:
> >> On Thu, Oct 27, 2011 at 9:54 PM, Martin Jambor <mjam...@suse.cz> wrote:
> >> > Hi,
> >> >
> >> > On Thu, Oct 27, 2011 at 11:06:02AM +0200, Richard Guenther wrote:
> >> >> On Thu, Oct 27, 2011 at 1:22 AM, Martin Jambor <mjam...@suse.cz> wrote:
> >> >> > Hi,
> >> >> >
> >> >> > I've been asked by Maxim Kuvyrkov to revive the following patch which
> >> >> > has not made it to 4.6.  Currently, when type based devirtualization
> >> >> > detects a potential type change, it simply gives up on gathering any
> >> >> > information on the object in question.  This patch adds an attempt to
> >> >> > actually detect the new type after the change.
> >> >> >
> >> >> > Maxim claimed this (and another patch I'll post tomorrow) noticeably
> >> >> > improved performance of some real code.  I can only offer a rather
> >> >> > artificial example in the attachment.  When the constructors are
> >> >> > inlined but the function multiply_matrices is not, this patch makes
> >> >> > the produced executable run for only 7 seconds instead of about 20 on
> >> >> > my 4 year old i686 desktop (with -Ofast).
> >> >> >
> >> >> > Anyway, the patch passes bootstrap and testsuite on x86_64-linux.
> >> >> > What do you think, is it a good idea for trunk now?
> >> >> >
> >> >> > Thanks,
> >> >> >
> >> >> > Martin
> >> >> >
> >> >> >
> >> >> > 2011-10-21  Martin Jambor  <mjam...@suse.cz>
> >> >> >
> >> >> >        * ipa-prop.c (type_change_info): New fields object, 
> >> >> > known_current_type
> >> >> >        and multiple_types_encountered.
> >> >> >        (extr_type_from_vtbl_ptr_store): New function.
> >> >> >        (check_stmt_for_type_change): Use it, set 
> >> >> > multiple_types_encountered if
> >> >> >        the result is different from the previous one.
> >> >> >        (detect_type_change): Renamed to detect_type_change_1. New 
> >> >> > parameter
> >> >> >        comp_type.  Set up new fields in tci, build known type jump
> >> >> >        functions if the new type can be identified.
> >> >> >        (detect_type_change): New function.
> >> >> >        * tree.h (DECL_CONTEXT): Comment new use.
> >> >> >
> >> >> >        * testsuite/g++.dg/ipa/devirt-c-1.C: Add dump scans.
> >> >> >        * testsuite/g++.dg/ipa/devirt-c-2.C: Likewise.
> >> >> >        * testsuite/g++.dg/ipa/devirt-c-7.C: New test.
> >> >> >
> >> >> >
> >> >> > Index: src/gcc/ipa-prop.c
> >> >> > ===================================================================
> >> >> > --- src.orig/gcc/ipa-prop.c
> >> >> > +++ src/gcc/ipa-prop.c
> >> >> > @@ -271,8 +271,17 @@ ipa_print_all_jump_functions (FILE *f)
> >> >> >
> >> >> >  struct type_change_info
> >> >> >  {
> >> >> > +  /* The declaration or SSA_NAME pointer of the base that we are 
> >> >> > checking for
> >> >> > +     type change.  */
> >> >> > +  tree object;
> >> >> > +  /* If we actually can tell the type that the object has changed 
> >> >> > to, it is
> >> >> > +     stored in this field.  Otherwise it remains NULL_TREE.  */
> >> >> > +  tree known_current_type;
> >> >> >   /* Set to true if dynamic type change has been detected.  */
> >> >> >   bool type_maybe_changed;
> >> >> > +  /* Set to true if multiple types have been encountered.  
> >> >> > known_current_type
> >> >> > +     must be disregarded in that case.  */
> >> >> > +  bool multiple_types_encountered;
> >> >> >  };
> >> >> >
> >> >> >  /* Return true if STMT can modify a virtual method table pointer.
> >> >> > @@ -338,6 +347,49 @@ stmt_may_be_vtbl_ptr_store (gimple stmt)
> >> >> >   return true;
> >> >> >  }
> >> >> >
> >> >> > +/* If STMT can be proved to be an assignment to the virtual method 
> >> >> > table
> >> >> > +   pointer of ANALYZED_OBJ and the type associated with the new table
> >> >> > +   identified, return the type.  Otherwise return NULL_TREE.  */
> >> >> > +
> >> >> > +static tree
> >> >> > +extr_type_from_vtbl_ptr_store (gimple stmt, tree analyzed_obj)
> >> >> > +{
> >> >> > +  tree lhs, t, obj;
> >> >> > +
> >> >> > +  if (!is_gimple_assign (stmt))
> >> >>
> >> >> gimple_assign_single_p (stmt)
> >> >
> >> > OK.
> >> >
> >> >>
> >> >> > +    return NULL_TREE;
> >> >> > +
> >> >> > +  lhs = gimple_assign_lhs (stmt);
> >> >> > +
> >> >> > +  if (TREE_CODE (lhs) != COMPONENT_REF)
> >> >> > +    return NULL_TREE;
> >> >> > +  obj = lhs;
> >> >> > +
> >> >> > +  if (!DECL_VIRTUAL_P (TREE_OPERAND (lhs, 1)))
> >> >> > +    return NULL_TREE;
> >> >> > +
> >> >> > +  do
> >> >> > +    {
> >> >> > +      obj = TREE_OPERAND (obj, 0);
> >> >> > +    }
> >> >> > +  while (TREE_CODE (obj) == COMPONENT_REF);
> >> >>
> >> >> You do not allow other components than component-refs (thus, for
> >> >> example an ARRAY_REF - that is for a reason?).  Please add
> >> >> a comment why.  Otherwise this whole sequence would look like
> >> >> it should be replaceable by get_base_address (obj).
> >> >>
> >> >
> >> > I guess I might have been overly conservative here, ARRAY_REFs are
> >> > fine.  get_base_address only digs into MEM_REFs if they are based on
> >> > an ADDR_EXPR while I do so always.  But I can check that either both
> >> > obj and analyzed_obj are a MEM_REF of the same SSA_NAME or they are
> >> > the same thing (i.e. the same decl)... which even feels a bit cleaner,
> >> > so I did that.
> >>
> >> Well, as you are looking for a must-change-type pattern I think you cannot
> >> simply ignore offsets.  Consider
> >>
> >> T a[10];
> >>
> >> new (T') (&a[9]);
> >> a[8]->foo();
> >>
> >> where the must-type-change on a[9] is _not_ changing the type of a[8]!
> >>
> >> Similar cases might happen with
> >>
> >> class Compound { T a; T b; };
> >>
> >> no?
> >>
> >> Please think about the difference must vs. may-type-change for these
> >> cases.  I'm not convinced that the must-type-change code is correct.
> >>
> >
> > I did, even though it's not always easy because the patch is so old.
> > However, the reasoning is as follows:
> >
> > 1) The callback will never report a known type if the "base address"
> > of the RHS of the assignment is not exactly the same declaration or
> > a MEM_REF of exactly the same SSA name.
> >
> > 2) The aliasing machinery is initialized to exactly the offset we are
> > interested in and the size of one pointer:
> >
> >  ao.offset = offset;
> >  ao.size = POINTER_SIZE;
> >
> > So, either the access which can alter the virtual table pointer is
> > based on a different pointer and then we will not attempt to claim we
> > know the type or the aliasing machinery invokes the callback only for
> > the correct offset, because it can disambiguate between those based on
> > the same thing.  Does that make sense?
> 
> Not for the latter - the alias machinery reports may-aliases, thus
> if you ask for [32, 36] then the alias machinery will invoke the
> callback for a[i].vptr as well (the access with a variable offset
> makes it [0, -1U], thus coverting [32, 46]).  You have to check for
> a must-alias relationship explicitely, which you cannot with the
> information you have, once a possibly variable offset component
> is visited.  Note that the alias machinery may validly invoke your
> callback for all stores, even those that trivially do not alias - saying
> they may-alias is still correct.
> 
> > (One further reason why I did not think about arrays that much is that
> > we will currently never attempt to search for BINFOs if there are
> > arrays involved in any way, though I understand we may change that in
> > the future.  BTW, I spent quite some time attempting to break this
> > code on Thursday but did not succeed even when I removed the check 1
> > and the analysis produced clearly wrong results because of how we
> > store the known type jump functions today and search for BINFOs only
> > very much later on, checking the expected type is there at the
> > expected offset in the type hierarchy of the type of the whole
> > declaration.  Of course, I understand the analysis has to be correct
> > nevertheless and I tried to make it conservative.)
> 
> So I think you should explicitely not handle all variable-offset components
> and pass down the actual offset of the pointer to the walker callback so
> it can verify that the offsets match.
> 

Fair enough, I did not take into account variable offsets.  This is
the updated patch, re-bootstrapped and re-tested on x86_64-linux
without any issues.  

Thanks,

Martin


2011-11-01  Martin Jambor  <mjam...@suse.cz>

        * ipa-prop.c (type_change_info): New fields offset, object,
        known_current_type and multiple_types_encountered.
        (extr_type_from_vtbl_ptr_store): New function.
        (check_stmt_for_type_change): Use it, set multiple_types_encountered if
        the result is different from the previous one.
        (detect_type_change): Renamed to detect_type_change_1. New parameter
        comp_type.  Set up new fields in tci, build known type jump
        functions if the new type can be identified.
        (detect_type_change): New function.
        * tree.h (DECL_CONTEXT): Comment new use.

        * testsuite/g++.dg/ipa/devirt-c-1.C: Add dump scans.
        * testsuite/g++.dg/ipa/devirt-c-2.C: Likewise.
        * testsuite/g++.dg/ipa/devirt-c-7.C: New test.
        * testsuite/g++.dg/ipa/devirt-c-8.C: Likewise.


Index: src/gcc/ipa-prop.c
===================================================================
--- src.orig/gcc/ipa-prop.c
+++ src/gcc/ipa-prop.c
@@ -271,8 +271,20 @@ ipa_print_all_jump_functions (FILE *f)
 
 struct type_change_info
 {
+  /* Offset into the object where there is the virtual method pointer we are
+     looking for.  */
+  HOST_WIDE_INT offset;
+  /* The declaration or SSA_NAME pointer of the base that we are checking for
+     type change.  */
+  tree object;
+  /* If we actually can tell the type that the object has changed to, it is
+     stored in this field.  Otherwise it remains NULL_TREE.  */
+  tree known_current_type;
   /* Set to true if dynamic type change has been detected.  */
   bool type_maybe_changed;
+  /* Set to true if multiple types have been encountered.  known_current_type
+     must be disregarded in that case.  */
+  bool multiple_types_encountered;
 };
 
 /* Return true if STMT can modify a virtual method table pointer.
@@ -338,6 +350,48 @@ stmt_may_be_vtbl_ptr_store (gimple stmt)
   return true;
 }
 
+/* If STMT can be proved to be an assignment to the virtual method table
+   pointer of ANALYZED_OBJ and the type associated with the new table
+   identified, return the type.  Otherwise return NULL_TREE.  */
+
+static tree
+extr_type_from_vtbl_ptr_store (gimple stmt, struct type_change_info *tci)
+{
+  HOST_WIDE_INT offset, size, max_size;
+  tree lhs, rhs, base;
+
+  if (!gimple_assign_single_p (stmt))
+    return NULL_TREE;
+
+  lhs = gimple_assign_lhs (stmt);
+  rhs = gimple_assign_rhs1 (stmt);
+  if (TREE_CODE (lhs) != COMPONENT_REF
+      || !DECL_VIRTUAL_P (TREE_OPERAND (lhs, 1))
+      || TREE_CODE (rhs) != ADDR_EXPR)
+    return NULL_TREE;
+  rhs = get_base_address (TREE_OPERAND (rhs, 0));
+  if (!rhs
+      || TREE_CODE (rhs) != VAR_DECL
+      || !DECL_VIRTUAL_P (rhs))
+    return NULL_TREE;
+
+  base = get_ref_base_and_extent (lhs, &offset, &size, &max_size);
+  if (offset != tci->offset
+      || size != POINTER_SIZE
+      || max_size != POINTER_SIZE)
+    return NULL_TREE;
+  if (TREE_CODE (base) == MEM_REF)
+    {
+      if (TREE_CODE (tci->object) != MEM_REF
+         || TREE_OPERAND (tci->object, 0) != TREE_OPERAND (base, 0))
+       return NULL_TREE;
+    }
+  else if (tci->object != base)
+    return NULL_TREE;
+
+  return DECL_CONTEXT (rhs);
+}
+
 /* Callback of walk_aliased_vdefs and a helper function for
    detect_type_change to check whether a particular statement may modify
    the virtual table pointer, and if possible also determine the new type of
@@ -352,6 +406,12 @@ check_stmt_for_type_change (ao_ref *ao A
 
   if (stmt_may_be_vtbl_ptr_store (stmt))
     {
+      tree type;
+      type = extr_type_from_vtbl_ptr_store (stmt, tci);
+      if (tci->type_maybe_changed
+         && type != tci->known_current_type)
+       tci->multiple_types_encountered = true;
+      tci->known_current_type = type;
       tci->type_maybe_changed = true;
       return true;
     }
@@ -359,16 +419,15 @@ check_stmt_for_type_change (ao_ref *ao A
     return false;
 }
 
-/* Detect whether the dynamic type of ARG has changed (before callsite CALL) by
-   looking for assignments to its virtual table pointer.  If it is, return true
-   and fill in the jump function JFUNC with relevant type information or set it
-   to unknown.  ARG is the object itself (not a pointer to it, unless
-   dereferenced).  BASE is the base of the memory access as returned by
-   get_ref_base_and_extent, as is the offset.  */
+
+
+/* Like detect_type_change but with extra argument COMP_TYPE which will become
+   the component type part of new JFUNC of dynamic type change is detected and
+   the new base type is identified.  */
 
 static bool
-detect_type_change (tree arg, tree base, gimple call,
-                   struct ipa_jump_func *jfunc, HOST_WIDE_INT offset)
+detect_type_change_1 (tree arg, tree base, tree comp_type, gimple call,
+                     struct ipa_jump_func *jfunc, HOST_WIDE_INT offset)
 {
   struct type_change_info tci;
   ao_ref ao;
@@ -381,8 +440,6 @@ detect_type_change (tree arg, tree base,
   if (!flag_devirtualize || !gimple_vuse (call))
     return false;
 
-  tci.type_maybe_changed = false;
-
   ao.ref = arg;
   ao.base = base;
   ao.offset = offset;
@@ -391,15 +448,45 @@ detect_type_change (tree arg, tree base,
   ao.ref_alias_set = -1;
   ao.base_alias_set = -1;
 
+  tci.offset = offset;
+  tci.object = get_base_address (arg);
+  tci.known_current_type = NULL_TREE;
+  tci.type_maybe_changed = false;
+  tci.multiple_types_encountered = false;
+
   walk_aliased_vdefs (&ao, gimple_vuse (call), check_stmt_for_type_change,
                      &tci, NULL);
   if (!tci.type_maybe_changed)
     return false;
 
-  jfunc->type = IPA_JF_UNKNOWN;
+  if (!tci.known_current_type
+      || tci.multiple_types_encountered
+      || offset != 0)
+    jfunc->type = IPA_JF_UNKNOWN;
+  else
+    {
+      jfunc->type = IPA_JF_KNOWN_TYPE;
+      jfunc->value.known_type.base_type = tci.known_current_type;
+      jfunc->value.known_type.component_type = comp_type;
+    }
+
   return true;
 }
 
+/* Detect whether the dynamic type of ARG has changed (before callsite CALL) by
+   looking for assignments to its virtual table pointer.  If it is, return true
+   and fill in the jump function JFUNC with relevant type information or set it
+   to unknown.  ARG is the object itself (not a pointer to it, unless
+   dereferenced).  BASE is the base of the memory access as returned by
+   get_ref_base_and_extent, as is the offset.  */
+
+static bool
+detect_type_change (tree arg, tree base, gimple call,
+                   struct ipa_jump_func *jfunc, HOST_WIDE_INT offset)
+{
+  return detect_type_change_1 (arg, base, TREE_TYPE (arg), call, jfunc, 
offset);
+}
+
 /* Like detect_type_change but ARG is supposed to be a non-dereferenced pointer
    SSA name (its dereference will become the base and the offset is assumed to
    be zero).  */
@@ -407,16 +494,19 @@ detect_type_change (tree arg, tree base,
 static bool
 detect_type_change_ssa (tree arg, gimple call, struct ipa_jump_func *jfunc)
 {
+  tree comp_type;
+
   gcc_checking_assert (TREE_CODE (arg) == SSA_NAME);
   if (!flag_devirtualize
       || !POINTER_TYPE_P (TREE_TYPE (arg))
       || TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) != RECORD_TYPE)
     return false;
 
+  comp_type = TREE_TYPE (TREE_TYPE (arg));
   arg = build2 (MEM_REF, ptr_type_node, arg,
-                build_int_cst (ptr_type_node, 0));
+               build_int_cst (ptr_type_node, 0));
 
-  return detect_type_change (arg, arg, call, jfunc, 0);
+  return detect_type_change_1 (arg, arg, comp_type, call, jfunc, 0);
 }
 
 /* Callback of walk_aliased_vdefs.  Flags that it has been invoked to the
Index: src/gcc/testsuite/g++.dg/ipa/devirt-c-1.C
===================================================================
--- src.orig/gcc/testsuite/g++.dg/ipa/devirt-c-1.C
+++ src/gcc/testsuite/g++.dg/ipa/devirt-c-1.C
@@ -1,7 +1,7 @@
 /* Verify that ipa-cp correctly detects the dynamic type of an object
    under construction when doing devirtualization.  */
 /* { dg-do run } */
-/* { dg-options "-O3 -fno-early-inlining -fno-inline"  } */
+/* { dg-options "-O3 -fno-early-inlining -fno-inline -fdump-ipa-cp 
-fdump-tree-optimized"  } */
 
 extern "C" void abort (void);
 
@@ -69,3 +69,8 @@ int main (int argc, char *argv[])
     bah ();
   return 0;
 }
+
+/* { dg-final { scan-ipa-dump "Discovered a virtual call to a known 
target.*A::foo"  "cp"  } } */
+/* { dg-final { scan-tree-dump-times "OBJ_TYPE_REF" 0 "optimized"} } */
+/* { dg-final { cleanup-ipa-dump "cp" } } */
+/* { dg-final { cleanup-tree-dump "optimized" } } */
Index: src/gcc/testsuite/g++.dg/ipa/devirt-c-2.C
===================================================================
--- src.orig/gcc/testsuite/g++.dg/ipa/devirt-c-2.C
+++ src/gcc/testsuite/g++.dg/ipa/devirt-c-2.C
@@ -1,7 +1,7 @@
 /* Verify that ipa-cp correctly detects the dynamic type of an object
    under construction when doing devirtualization.  */
 /* { dg-do run } */
-/* { dg-options "-O3 -fno-early-inlining -fno-inline"  } */
+/* { dg-options "-O3 -fno-early-inlining -fno-inline -fdump-ipa-cp 
-fdump-tree-optimized"  } */
 
 extern "C" void abort (void);
 
@@ -77,3 +77,8 @@ int main (int argc, char *argv[])
     bah ();
   return 0;
 }
+
+/* { dg-final { scan-ipa-dump "Discovered a virtual call to a known 
target.*A::foo"  "cp"  } } */
+/* { dg-final { scan-tree-dump-times "OBJ_TYPE_REF" 0 "optimized"} } */
+/* { dg-final { cleanup-ipa-dump "cp" } } */
+/* { dg-final { cleanup-tree-dump "optimized" } } */
Index: src/gcc/tree.h
===================================================================
--- src.orig/gcc/tree.h
+++ src/gcc/tree.h
@@ -2685,7 +2685,9 @@ struct function;
     nodes, this points to either the FUNCTION_DECL for the containing
     function, the RECORD_TYPE or UNION_TYPE for the containing type, or
     NULL_TREE or a TRANSLATION_UNIT_DECL if the given decl has "file
-    scope".  */
+    scope".  In particular, for VAR_DECLs which are virtual table pointers
+    (they have DECL_VIRTUAL set), we use DECL_CONTEXT to determine the type
+    they belong to.  */
 #define DECL_CONTEXT(NODE) (DECL_MINIMAL_CHECK (NODE)->decl_minimal.context)
 #define DECL_FIELD_CONTEXT(NODE) \
   (FIELD_DECL_CHECK (NODE)->decl_minimal.context)
Index: src/gcc/testsuite/g++.dg/ipa/devirt-c-7.C
===================================================================
--- /dev/null
+++ src/gcc/testsuite/g++.dg/ipa/devirt-c-7.C
@@ -0,0 +1,87 @@
+/* Verify that ipa-cp will not get confused by placement new constructing an
+   object within another one when looking for dynamic type change .  */
+/* { dg-do run } */
+/* { dg-options "-O3 -Wno-attributes"  } */
+
+extern "C" void abort (void);
+namespace std {
+  typedef __SIZE_TYPE__ size_t;
+}
+inline void* __attribute__ ((always_inline))
+operator new(std::size_t, void* __p) throw()
+{
+  return __p;
+}
+
+class A
+{
+public:
+  char data[256];
+  A();
+  virtual int foo (int i);
+};
+
+class B : public A
+{
+public:
+  virtual int foo (int i);
+};
+
+class C
+{
+public:
+  C();
+  virtual double foo (double i);
+};
+
+int A::foo (int i)
+{
+  return i + 1;
+}
+
+int B::foo (int i)
+{
+  return i + 2;
+}
+
+double C::foo (double i)
+{
+  return i + 3.5;
+}
+
+static int __attribute__ ((noinline)) middleman (class A *obj, int i)
+{
+  return obj->foo (i);
+}
+
+int __attribute__ ((noinline,noclone)) get_input(void)
+{
+  return 1;
+}
+
+__attribute__ ((always_inline)) C::C ()
+{
+}
+
+A::A ()
+{
+}
+
+static  __attribute__ ((noinline)) void bah ()
+{
+  class B b;
+
+  C *c = new ((void *) &b.data) C;
+
+  if (middleman (&b, get_input ()) != 3)
+    abort ();
+}
+
+int main (int argc, char *argv[])
+{
+  int i;
+
+  for (i = 0; i < 10; i++)
+    bah ();
+  return 0;
+}
Index: src/gcc/testsuite/g++.dg/ipa/devirt-c-8.C
===================================================================
--- /dev/null
+++ src/gcc/testsuite/g++.dg/ipa/devirt-c-8.C
@@ -0,0 +1,82 @@
+/* Verify that ipa-cp correctly detects the dynamic type of an object
+   under construction when doing devirtualization.  */
+/* { dg-do run } */
+/* { dg-options "-O3 -fno-early-inlining -fno-inline -fdump-ipa-cp 
-fdump-tree-optimized"  } */
+
+extern "C" void abort (void);
+
+class A
+{
+public:
+  int data;
+  A();
+  virtual int foo (int i);
+};
+
+class B : public A
+{
+public:
+  B();
+  virtual int foo (int i);
+};
+
+class C : public A
+{
+public:
+  virtual int foo (int i);
+};
+
+int A::foo (int i)
+{
+  return i + 1;
+}
+
+int B::foo (int i)
+{
+  return i + 2;
+}
+
+int C::foo (int i)
+{
+  return i + 3;
+}
+
+static int __attribute__ ((noinline))
+middleman (class A *obj, int i)
+{
+  return obj->foo (i);
+}
+
+int __attribute__ ((noinline,noclone)) get_input(void)
+{
+  return 1;
+}
+
+inline __attribute__ ((always_inline)) A::A ()
+{
+  if (middleman (this, get_input ()) != 2)
+    abort ();
+}
+
+inline __attribute__ ((always_inline)) B::B ()
+{
+}
+
+static void bah ()
+{
+  class B b;
+}
+
+int main (int argc, char *argv[])
+{
+  int i;
+
+  for (i = 0; i < 10; i++)
+    bah ();
+  return 0;
+}
+
+/* { dg-final { scan-ipa-dump "Discovered a virtual call to a known 
target.*A::foo"  "cp"  } } */
+/* { dg-final { scan-tree-dump-times "OBJ_TYPE_REF" 0 "optimized"} } */
+/* { dg-final { cleanup-ipa-dump "cp" } } */
+/* { dg-final { cleanup-tree-dump "optimized" } } */

Reply via email to