[PATCH, devirtualization] Detect the new type in type change detection

Richard Guenther richard.guenther@gmail.com
Thu Oct 27 11:02:00 GMT 2011


On Thu, Oct 27, 2011 at 1:22 AM, Martin Jambor <mjambor@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  <mjambor@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)

> +    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).

> +  if (TREE_CODE (obj) == MEM_REF)
> +    obj = TREE_OPERAND (obj, 0);
> +  if (TREE_CODE (obj) == ADDR_EXPR)
> +    obj = TREE_OPERAND (obj, 0);
> +  if (obj != analyzed_obj)
> +    return NULL_TREE;
> +
> +  t = gimple_assign_rhs1 (stmt);
> +  if (TREE_CODE (t) != ADDR_EXPR)
> +    return NULL_TREE;
> +  t = get_base_address (TREE_OPERAND (t, 0));

Do we actually ever store sth else than the plain address of a
virtual table (thus, &decl)?  If so, what difference makes the offset
into the virtual table we get in this way?  I ask, because ...

> +  if (!t || TREE_CODE (t) != VAR_DECL || !DECL_VIRTUAL_P (t))
> +    return NULL_TREE;
> +
> +  return DECL_CONTEXT (t);

... DECL_CONTEXT (t) is of course independent of any offset.

Thus I think you should drop the call to get_base_address () and
instead just look at TREE_OPERAND (t, 0).

> +}
> +
>  /* 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 +404,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->object);
> +      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,19 +417,19 @@ 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;
> +  tree t;
>
>   gcc_checking_assert (DECL_P (arg)
>                       || TREE_CODE (arg) == MEM_REF
> @@ -381,8 +439,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 +447,51 @@ detect_type_change (tree arg, tree base,
>   ao.ref_alias_set = -1;
>   ao.base_alias_set = -1;
>
> +  t = arg;
> +  while (handled_component_p (t))
> +    t = TREE_OPERAND (t, 0);

Here you are using handled_component_p, thus allow ARRAY_REFs ...
but you are not trying to distinguish a[1] from a[2] anywhere, so this
looks bogus and instead should look like the case above (with a comment
as to why looking at ARRAY_REFs would be bogus).

Thanks,
Richard.

> +  if (TREE_CODE (t) == MEM_REF)
> +    t = TREE_OPERAND (t, 0);
> +  if (TREE_CODE (t) == ADDR_EXPR)
> +    t = TREE_OPERAND (t, 0);
> +  tci.object = t;
> +  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 +499,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
> @@ -2678,7 +2678,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;
> +}
>



More information about the Gcc-patches mailing list