diff mbox

[RFC] Issues with intraprocedural devirtualization

Message ID 20130817214457.GA7313@kam.mff.cuni.cz
State New
Headers show

Commit Message

Jan Hubicka Aug. 17, 2013, 9:44 p.m. UTC
Hi,
this patch tries to make gimple_extract_devirt_binfo_from_cst little bit more
sane and make it match bit more cases.  Currently we seem to be able to
devirtualize only if the type in question has no basetypes (not even some not
having virtual methods at all) and it is the initial type implementing the
given virtual method (i.e. we even refuse any derivation not giving new
implementation of the virtual method in question).  This is bit silly.

What the current implemntation does

Comments

Jason Merrill Aug. 18, 2013, 12:56 a.m. UTC | #1
On 08/17/2013 05:44 PM, Jan Hubicka wrote:
>   1) we want the type to not have base because we may have inlined the constructor.
>      During construction the vtables are filled by base's vtable and thus we can
>      not simply devirtualize based on the final virtual table without proving that
>      constructor was not (partially) inlined.

If the constructor is inlined into the current function, then we can see 
the most recent assignment to the vptr and use that for devirtualization.

> I do not know if one can do
> something like having automatic variable of class A and use placement new
> to change it to class B.

This is something of a grey area in the standard, with a few defect 
reports yet to be resolved.  I think it should be undefined behavior.

> Finally I decided to replace the lookup of base binfo by call to
> get_binfo_at_offset.  This is not possible.  For example my base
> variable can be:
>
> struct {class A a; class B b} var;
>
> and offset 10 may point to class B.  Here I get an ICE since TYPE_BINFO
> of the structure is NULL (it is not class).

I don't understand.  In C++ a struct is a class.

> I wonder if we can track functions that return pointers/values of objects
>     exactly of given type (i.e. no derivations).

If the function returns by value, it's always a value of that exact type.

> Is the dynamic type upon return from constructor always known to be of
>     constructor's type?

Yes.

>  We may want to introduce assert_type_expr use like:
>
>     obj_ptr = assert_type_expr<class A> obj_ptr;
>
>     that can be dropped by FE for us to drive those more interesting cases, like
>     partial construction.

I guess we would need to be careful to insert those after vptr 
assignments within the constructor body, as well.

Jason
Gabriel Dos Reis Aug. 18, 2013, 4:11 a.m. UTC | #2
On Sat, Aug 17, 2013 at 7:56 PM, Jason Merrill <jason@redhat.com> wrote:

>> I do not know if one can do
>> something like having automatic variable of class A and use placement new
>> to change it to class B.
>
>
> This is something of a grey area in the standard, with a few defect reports
> yet to be resolved.  I think it should be undefined behavior.

Jason, could you remind me of the CWG issue numbers for these?
It is also of interest for SG12, even if it isn't going to come withe
a different answer.

-- Gaby
Richard Biener Aug. 18, 2013, 6:44 a.m. UTC | #3
Jason Merrill <jason@redhat.com> wrote:
>On 08/17/2013 05:44 PM, Jan Hubicka wrote:
>>   1) we want the type to not have base because we may have inlined
>the constructor.
>>      During construction the vtables are filled by base's vtable and
>thus we can
>>      not simply devirtualize based on the final virtual table without
>proving that
>>      constructor was not (partially) inlined.
>
>If the constructor is inlined into the current function, then we can
>see 
>the most recent assignment to the vptr and use that for
>devirtualization.
>
>> I do not know if one can do
>> something like having automatic variable of class A and use placement
>new
>> to change it to class B.
>
>This is something of a grey area in the standard, with a few defect 
>reports yet to be resolved.  I think it should be undefined behavior.

I'm curious if special.casing the automatic non-pod type case is worth the trouble.  At least you need to support placement new on pod class members, in which case you need to be careful with which cases you can reliably detectat the gimple level.  As always - look at past dynamic type bugs we had with boost.

Richard.


>> Finally I decided to replace the lookup of base binfo by call to
>> get_binfo_at_offset.  This is not possible.  For example my base
>> variable can be:
>>
>> struct {class A a; class B b} var;
>>
>> and offset 10 may point to class B.  Here I get an ICE since
>TYPE_BINFO
>> of the structure is NULL (it is not class).
>
>I don't understand.  In C++ a struct is a class.
>
>> I wonder if we can track functions that return pointers/values of
>objects
>>     exactly of given type (i.e. no derivations).
>
>If the function returns by value, it's always a value of that exact
>type.
>
>> Is the dynamic type upon return from constructor always known to be
>of
>>     constructor's type?
>
>Yes.
>
>>  We may want to introduce assert_type_expr use like:
>>
>>     obj_ptr = assert_type_expr<class A> obj_ptr;
>>
>>     that can be dropped by FE for us to drive those more interesting
>cases, like
>>     partial construction.
>
>I guess we would need to be careful to insert those after vptr 
>assignments within the constructor body, as well.
>
>Jason
Jan Hubicka Aug. 18, 2013, 8:53 a.m. UTC | #4
> On 08/17/2013 05:44 PM, Jan Hubicka wrote:
> >  1) we want the type to not have base because we may have inlined the constructor.
> >     During construction the vtables are filled by base's vtable and thus we can
> >     not simply devirtualize based on the final virtual table without proving that
> >     constructor was not (partially) inlined.
> 
> If the constructor is inlined into the current function, then we can
> see the most recent assignment to the vptr and use that for
> devirtualization.

In most cases, yes.  There are some special issues, like partial inlining
(where the "wrong" assignment gets inlined, but the final assignment not) But I
think tracking inlining those (it will need to make middle end aware of
constructors that I think is good idea anyway) and leaving those to the
constant propagation should work well enough in practice.

> 
> >I do not know if one can do
> >something like having automatic variable of class A and use placement new
> >to change it to class B.
> 
> This is something of a grey area in the standard, with a few defect
> reports yet to be resolved.  I think it should be undefined
> behavior.

I would preffer it being so ;)

> 
> >Finally I decided to replace the lookup of base binfo by call to
> >get_binfo_at_offset.  This is not possible.  For example my base
> >variable can be:
> >
> >struct {class A a; class B b} var;
> >
> >and offset 10 may point to class B.  Here I get an ICE since TYPE_BINFO
> >of the structure is NULL (it is not class).
> 
> I don't understand.  In C++ a struct is a class.

You are right.  I get the ICE here only why I handle arrays and unions, too.
(as I do in my tree, but not in the patches posted). In the example above
the struct seems to have TYPE_BINFO even though it is useless.

Since handling arrays and union seems to make sense and resolves some real
testcases from firefox, I think we could keep the tree structured in a way
making this possible.  I.e. having the type walk as proposed in the patch
instead of calling get_binfo_at_offset.
> 
> >I wonder if we can track functions that return pointers/values of objects
> >    exactly of given type (i.e. no derivations).
> 
> If the function returns by value, it's always a value of that exact type.

OK, good!
> 
> >Is the dynamic type upon return from constructor always known to be of
> >    constructor's type?
> 
> Yes.
> 
> > We may want to introduce assert_type_expr use like:
> >
> >    obj_ptr = assert_type_expr<class A> obj_ptr;
> >
> >    that can be dropped by FE for us to drive those more interesting cases, like
> >    partial construction.
> 
> I guess we would need to be careful to insert those after vptr
> assignments within the constructor body, as well.

Yes, if we go this way, we will need C++ FE to produce the asserts.

The original patch seems resonable?

Honza
> 
> Jason
Jan Hubicka Aug. 18, 2013, 8:55 a.m. UTC | #5
> > On 08/17/2013 05:44 PM, Jan Hubicka wrote:
> > >  1) we want the type to not have base because we may have inlined the constructor.
> > >     During construction the vtables are filled by base's vtable and thus we can
> > >     not simply devirtualize based on the final virtual table without proving that
> > >     constructor was not (partially) inlined.
> > 
> > If the constructor is inlined into the current function, then we can
> > see the most recent assignment to the vptr and use that for
> > devirtualization.
> 
> In most cases, yes.  There are some special issues, like partial inlining
> (where the "wrong" assignment gets inlined, but the final assignment not) But I
> think tracking inlining those (it will need to make middle end aware of
			  ^^^^^ of constructors, partial or not.
> constructors that I think is good idea anyway) and leaving those to the
> constant propagation should work well enough in practice.

Basicaly if ctor of given type was inlined, we will make the assumption that the
type can be partially constructed.
> > >Finally I decided to replace the lookup of base binfo by call to
> > >get_binfo_at_offset.  This is not possible.  For example my base
> > >variable can be:
> > >
> > >struct {class A a; class B b} var;
> > >
> > >and offset 10 may point to class B.  Here I get an ICE since TYPE_BINFO
> > >of the structure is NULL (it is not class).
> > 
> > I don't understand.  In C++ a struct is a class.
> 
> You are right.  I get the ICE here only why I handle arrays and unions, too.
				          ^^^ when
> (as I do in my tree, but not in the patches posted). In the example above
> the struct seems to have TYPE_BINFO even though it is useless.
> 
> Since handling arrays and union seems to make sense and resolves some real
> testcases from firefox, I think we could keep the tree structured in a way
> making this possible.  I.e. having the type walk as proposed in the patch
> instead of calling get_binfo_at_offset.

Honza
Jan Hubicka Aug. 19, 2013, 9:10 a.m. UTC | #6
Hi,
here is variant of patch that drops the field walking from
gimple_extract_devirt_binfo_from_cst completely. As pointed out
by Jason, it is pointless since all structures have BINFO in C++
and thus get_binfo_at_offset will do the job.

I would like to return the code back eventually to handle arrays&unions
but that can be done incrementally (and this is not the only place that
sufers from the problem)

Martin: I am still not quite certain about the dynamic type changing logic.
if this is the case ipa-prop needs to deal with and it handles only 0 offsets
within the outer type, I guess it can just test the offset by itself?

Honza

Bootstrapped/regtested x86_64-linux, OK?

	* ipa-cp.c (ipa_get_indirect_edge_target_1): Update use
	of gimple_extract_devirt_binfo_from_cst.
	* gimple-fold.c (gimple_extract_devirt_binfo_from_cst): Rework.
	(gimple_fold_call): Update use of gimple_extract_devirt_binfo_from_cst.
	* ipa-prop.c (try_make_edge_direct_virtual_call): Likewise.
	* gimple.h (gimple_extract_devirt_binfo_from_cst): Update.

Index: ipa-cp.c
===================================================================
*** ipa-cp.c	(revision 201814)
--- ipa-cp.c	(working copy)
*************** ipa_get_indirect_edge_target_1 (struct c
*** 1541,1554 ****
    if (TREE_CODE (t) != TREE_BINFO)
      {
        tree binfo;
        binfo = gimple_extract_devirt_binfo_from_cst
! 		 (t, ie->indirect_info->otr_type);
        if (!binfo)
  	return NULL_TREE;
!       binfo = get_binfo_at_offset (binfo, anc_offset, otr_type);
!       if (!binfo)
  	return NULL_TREE;
!       return gimple_get_virt_method_for_binfo (token, binfo);
      }
    else
      {
--- 1541,1564 ----
    if (TREE_CODE (t) != TREE_BINFO)
      {
        tree binfo;
+       tree target, base_target;
        binfo = gimple_extract_devirt_binfo_from_cst
! 		 (t, ie->indirect_info->otr_type,
! 		  ie->indirect_info->offset);
        if (!binfo)
  	return NULL_TREE;
!       target = gimple_get_virt_method_for_binfo (token, binfo);
!       if (!target)
! 	return NULL_TREE;
!       /* Constructors may be partially inlined.  We do not track
!          if type is in construction and during that time the
! 	 virtual table may correspond to virtual table of the
! 	 base type.  */
!       base_target = gimple_get_virt_method_for_binfo
! 		     (token, TYPE_BINFO (ie->indirect_info->otr_type));
!       if (base_target != target)
  	return NULL_TREE;
!       return target;
      }
    else
      {
Index: gimple-fold.c
===================================================================
*** gimple-fold.c	(revision 201814)
--- gimple-fold.c	(working copy)
*************** gimple_fold_builtin (gimple stmt)
*** 1006,1021 ****
  /* Return a binfo to be used for devirtualization of calls based on an object
     represented by a declaration (i.e. a global or automatically allocated one)
     or NULL if it cannot be found or is not safe.  CST is expected to be an
!    ADDR_EXPR of such object or the function will return NULL.  Currently it is
!    safe to use such binfo only if it has no base binfo (i.e. no ancestors)
!    EXPECTED_TYPE is type of the class virtual belongs to.  */
  
  tree
! gimple_extract_devirt_binfo_from_cst (tree cst, tree expected_type)
  {
    HOST_WIDE_INT offset, size, max_size;
!   tree base, type, binfo;
!   bool last_artificial = false;
  
    if (!flag_devirtualize
        || TREE_CODE (cst) != ADDR_EXPR
--- 1006,1025 ----
  /* Return a binfo to be used for devirtualization of calls based on an object
     represented by a declaration (i.e. a global or automatically allocated one)
     or NULL if it cannot be found or is not safe.  CST is expected to be an
!    ADDR_EXPR of such object or the function will return NULL.
! 
!    It is up to the caller to check for absence of dynamic type changes.
!    Because constructors may be partially inlined and the virtual tables
!    during construction may be overwritten by virtual tables by base types,
!    it is also up to caller to verify that either all base types have
!    the same virtual method or that this does not happen.  */
  
  tree
! gimple_extract_devirt_binfo_from_cst (tree cst, tree expected_type,
! 				      HOST_WIDE_INT otr_offset)
  {
    HOST_WIDE_INT offset, size, max_size;
!   tree base, type;
  
    if (!flag_devirtualize
        || TREE_CODE (cst) != ADDR_EXPR
*************** gimple_extract_devirt_binfo_from_cst (tr
*** 1028,1074 ****
    if (!DECL_P (base)
        || max_size == -1
        || max_size != size
!       || TREE_CODE (type) != RECORD_TYPE)
!     return NULL_TREE;
! 
!   /* Find the sub-object the constant actually refers to and mark whether it is
!      an artificial one (as opposed to a user-defined one).  */
!   while (true)
!     {
!       HOST_WIDE_INT pos, size;
!       tree fld;
! 
!       if (types_same_for_odr (type, expected_type))
! 	break;
!       if (offset < 0)
! 	return NULL_TREE;
! 
!       for (fld = TYPE_FIELDS (type); fld; fld = DECL_CHAIN (fld))
! 	{
! 	  if (TREE_CODE (fld) != FIELD_DECL)
! 	    continue;
! 
! 	  pos = int_bit_position (fld);
! 	  size = tree_low_cst (DECL_SIZE (fld), 1);
! 	  if (pos <= offset && (pos + size) > offset)
! 	    break;
! 	}
!       if (!fld || TREE_CODE (TREE_TYPE (fld)) != RECORD_TYPE)
! 	return NULL_TREE;
! 
!       last_artificial = DECL_ARTIFICIAL (fld);
!       type = TREE_TYPE (fld);
!       offset -= pos;
!     }
!   /* Artificial sub-objects are ancestors, we do not want to use them for
!      devirtualization, at least not here.  */
!   if (last_artificial)
!     return NULL_TREE;
!   binfo = TYPE_BINFO (type);
!   if (!binfo || BINFO_N_BASE_BINFOS (binfo) > 0)
      return NULL_TREE;
!   else
!     return binfo;
  }
  
  /* Attempt to fold a call statement referenced by the statement iterator GSI.
--- 1032,1042 ----
    if (!DECL_P (base)
        || max_size == -1
        || max_size != size
!       || TREE_CODE (type) != RECORD_TYPE
!       || !TYPE_BINFO (type))
      return NULL_TREE;
!   return get_binfo_at_offset (TYPE_BINFO (type),
! 			      offset + otr_offset, expected_type);
  }
  
  /* Attempt to fold a call statement referenced by the statement iterator GSI.
*************** gimple_fold_call (gimple_stmt_iterator *
*** 1108,1121 ****
        else
  	{
  	  tree obj = OBJ_TYPE_REF_OBJECT (callee);
  	  tree binfo = gimple_extract_devirt_binfo_from_cst
! 		 (obj, obj_type_ref_class (callee));
  	  if (binfo)
  	    {
  	      HOST_WIDE_INT token
  		= TREE_INT_CST_LOW (OBJ_TYPE_REF_TOKEN (callee));
  	      tree fndecl = gimple_get_virt_method_for_binfo (token, binfo);
! 	      if (fndecl)
  		{
  		  gimple_call_set_fndecl (stmt, fndecl);
  		  changed = true;
--- 1076,1095 ----
        else
  	{
  	  tree obj = OBJ_TYPE_REF_OBJECT (callee);
+ 	  tree class_type = obj_type_ref_class (callee);
  	  tree binfo = gimple_extract_devirt_binfo_from_cst
! 		 (obj, class_type, 0);
  	  if (binfo)
  	    {
  	      HOST_WIDE_INT token
  		= TREE_INT_CST_LOW (OBJ_TYPE_REF_TOKEN (callee));
  	      tree fndecl = gimple_get_virt_method_for_binfo (token, binfo);
! 	      tree base_fndecl = gimple_get_virt_method_for_binfo (token, TYPE_BINFO (class_type));
! 	      /* Constructors may be partially inlined.  We do not track
! 		 if type is in construction and during that time the
! 		 virtual table may correspond to virtual table of the
! 		 base type.  */
! 	      if (fndecl && base_fndecl == fndecl)
  		{
  		  gimple_call_set_fndecl (stmt, fndecl);
  		  changed = true;
Index: ipa-prop.c
===================================================================
*** ipa-prop.c	(revision 201814)
--- ipa-prop.c	(working copy)
*************** try_make_edge_direct_virtual_call (struc
*** 2444,2450 ****
  				   struct ipa_jump_func *jfunc,
  				   struct ipa_node_params *new_root_info)
  {
!   tree binfo, target;
  
    binfo = ipa_value_from_jfunc (new_root_info, jfunc);
  
--- 2444,2450 ----
  				   struct ipa_jump_func *jfunc,
  				   struct ipa_node_params *new_root_info)
  {
!   tree binfo, target, base_target = NULL;
  
    binfo = ipa_value_from_jfunc (new_root_info, jfunc);
  
*************** try_make_edge_direct_virtual_call (struc
*** 2454,2472 ****
    if (TREE_CODE (binfo) != TREE_BINFO)
      {
        binfo = gimple_extract_devirt_binfo_from_cst
! 		 (binfo, ie->indirect_info->otr_type);
        if (!binfo)
          return NULL;
      }
! 
!   binfo = get_binfo_at_offset (binfo, ie->indirect_info->offset,
! 			       ie->indirect_info->otr_type);
    if (binfo)
      target = gimple_get_virt_method_for_binfo (ie->indirect_info->otr_token,
  					       binfo);
    else
      return NULL;
  
    if (target)
      return ipa_make_edge_direct_to_target (ie, target);
    else
--- 2454,2484 ----
    if (TREE_CODE (binfo) != TREE_BINFO)
      {
        binfo = gimple_extract_devirt_binfo_from_cst
! 		 (binfo, ie->indirect_info->otr_type,
! 		  ie->indirect_info->offset);
        if (!binfo)
          return NULL;
+       /* Constructors may be partially inlined.  We do not track
+          if type is in construction and during that time the
+ 	 virtual table may correspond to virtual table of the
+ 	 base type.  */
+       base_target = gimple_get_virt_method_for_binfo (ie->indirect_info->otr_token,
+ 						      TYPE_BINFO (ie->indirect_info->otr_type));
+       if (!base_target)
+ 	return NULL;
      }
!   else
!     binfo = get_binfo_at_offset (binfo, ie->indirect_info->offset,
! 				 ie->indirect_info->otr_type);
    if (binfo)
      target = gimple_get_virt_method_for_binfo (ie->indirect_info->otr_token,
  					       binfo);
    else
      return NULL;
  
+   if (base_target && target != base_target)
+     return NULL;
+ 
    if (target)
      return ipa_make_edge_direct_to_target (ie, target);
    else
Index: gimple.h
===================================================================
*** gimple.h	(revision 201814)
--- gimple.h	(working copy)
*************** unsigned get_gimple_rhs_num_ops (enum tr
*** 854,860 ****
  gimple gimple_alloc_stat (enum gimple_code, unsigned MEM_STAT_DECL);
  const char *gimple_decl_printable_name (tree, int);
  tree gimple_get_virt_method_for_binfo (HOST_WIDE_INT, tree);
! tree gimple_extract_devirt_binfo_from_cst (tree, tree);
  
  /* Returns true iff T is a scalar register variable.  */
  extern bool is_gimple_reg (tree);
--- 854,860 ----
  gimple gimple_alloc_stat (enum gimple_code, unsigned MEM_STAT_DECL);
  const char *gimple_decl_printable_name (tree, int);
  tree gimple_get_virt_method_for_binfo (HOST_WIDE_INT, tree);
! tree gimple_extract_devirt_binfo_from_cst (tree, tree, HOST_WIDE_INT);
  
  /* Returns true iff T is a scalar register variable.  */
  extern bool is_gimple_reg (tree);
Jan Hubicka Aug. 22, 2013, 10:42 p.m. UTC | #7
Hi,
I went through some statistics on firefox build (it is a source combining many coding styles).
I was basically curious where we do devirtualization.  The result is:

Before inline (i.e. important devirtualization)
    624 ssa-pre devirt 0
        this is interaprocedural devirutalization happening during early FRE by propagating
        constant accesses into vtables entries. (i.e. not type based at all)

     10 ssa-pre devirt2 0
        this is type based intraprocedural analysis during early optimizations
    177 ipa-prop devirt
	devirtualization in ipa-cp
    243 ipa-cp inline devirt
        this is devirtualization happening at IPA stage during inlining

After inline (i.e. devirtualization where we missed the chance to do something useful)
     82 gimple-fold devirt 1
	this is the gimple-fold function in question (it also run pre-inline but apparently
	always fail.  I will try the proposed patch and sed updated stats tomorrow)

     27 ipa-prop intra devirt 1
	intraprocedural type based analysis
   1569 ssa-pre devirt 1
	this is interaprocedural devirutalization happening during late FRE (not type based)

So overall type based analyssi accounts 430 devirtualizations pre-inline and 109 post inline.
Low level propagation of vtable accesses gets 624 pre-inline and 1569 post inline.

Obviously the post inline numbers shows that we miss majority of interesting cases.
I hope we can noticeably improve this.

I am re-building with the proposed change now.

Honza
Jan Hubicka Aug. 23, 2013, 2:27 p.m. UTC | #8
> Hi,
> I went through some statistics on firefox build (it is a source combining many coding styles).
> I was basically curious where we do devirtualization.  The result is:
> 
> Before inline (i.e. important devirtualization)
>     624 ssa-pre devirt 0
>         this is interaprocedural devirutalization happening during early FRE by propagating
>         constant accesses into vtables entries. (i.e. not type based at all)
> 
>      10 ssa-pre devirt2 0
>         this is type based intraprocedural analysis during early optimizations
>     177 ipa-prop devirt
> 	devirtualization in ipa-cp
>     243 ipa-cp inline devirt
>         this is devirtualization happening at IPA stage during inlining

The promesed stats with this change.
     59 Single type devirt
	this is my new code devirtualizing types in anonymous namespaces when there is only
	one node.  I will send separate patch for this.
    696 ssa-pre devirt 0
	11% better than w/o patch (probably because of more early inline)
    537 ipa-cp inline devirt
	120% better than w/o patch
      5 ssa-pre devirt2 0
	50% less probably because I handle it earlier
    309 ipa-prop devirt
	74% more
	
     10 gimple-fold devirt2 0
	type based code in gimple-fold. It now does something.

> 
> After inline (i.e. devirtualization where we missed the chance to do something useful)
>      82 gimple-fold devirt 1
> 	this is the gimple-fold function in question (it also run pre-inline but apparently
> 	always fail.  I will try the proposed patch and sed updated stats tomorrow)
I was having trakcing bug here.  This is standard folding based propagation.
The type based path never suceeded.
> 
>      27 ipa-prop intra devirt 1
> 	intraprocedural type based analysis
>    1569 ssa-pre devirt 1
> 	this is interaprocedural devirutalization happening during late FRE (not type based)

     83 gimple-fold devirt 1
	One more.
      7 gimple-fold devirt2 1
	type based path now does something
     27 ssa-pre devirt2 1
	type based path, no change.
   1847 ssa-pre devirt 1
	17% up.
> 
> So overall type based analyssi accounts 430 devirtualizations pre-inline and 109 post inline.
> Low level propagation of vtable accesses gets 624 pre-inline and 1569 post inline.
> 
> Obviously the post inline numbers shows that we miss majority of interesting cases.
> I hope we can noticeably improve this.
> 
> I am re-building with the proposed change now.
> 
> Honza
Martin Jambor Aug. 23, 2013, 3:05 p.m. UTC | #9
Hi,

On Mon, Aug 19, 2013 at 11:10:31AM +0200, Jan Hubicka wrote:
> Hi,
> here is variant of patch that drops the field walking from
> gimple_extract_devirt_binfo_from_cst completely. As pointed out
> by Jason, it is pointless since all structures have BINFO in C++
> and thus get_binfo_at_offset will do the job.
> 
> I would like to return the code back eventually to handle arrays&unions
> but that can be done incrementally (and this is not the only place that
> sufers from the problem)
> 
> Martin: I am still not quite certain about the dynamic type changing logic.
> if this is the case ipa-prop needs to deal with and it handles only 0 offsets
> within the outer type, I guess it can just test the offset by itself?

In these cases, devirtualization is done according ipa invariants, no
dynamic type checks are in place, we did away with requiring the
artificial-ness of the field we devirtualize according to.
I believe your new check that the function has not been overridden is
sufficient and superior.

Thanks,

Martin

> 
> Honza
> 
> Bootstrapped/regtested x86_64-linux, OK?
> 
> 	* ipa-cp.c (ipa_get_indirect_edge_target_1): Update use
> 	of gimple_extract_devirt_binfo_from_cst.
> 	* gimple-fold.c (gimple_extract_devirt_binfo_from_cst): Rework.
> 	(gimple_fold_call): Update use of gimple_extract_devirt_binfo_from_cst.
> 	* ipa-prop.c (try_make_edge_direct_virtual_call): Likewise.
> 	* gimple.h (gimple_extract_devirt_binfo_from_cst): Update.
diff mbox

Patch

===================================

gimple_extract_devirt_binfo_from_cst looks for address expression that object
used for virtual method comes from.  It sees something like

 &var + 10;

It looks at the type of VAR and tries to look for a field at offset 10.  If it
suceeds it verify that the field is not artificial (i.e. it is not base of some
outer type present in var) and that its BINFO has no derived types (it basically
has to be the original type defining the virtual method and the type can not
be derived at all).

After this happen the caller proceeds by calling get_binfo_at_offset and by
lookup of virtual call from vtable found.

Why it does so
==============

I was always confused on why gimple_extract_devirt_binfo_from_cst apparently
duplicates logic of get_binfo_at_offset and why it has the limitations.
Disussing this with Martin, it is about dynamic type changes.  My understanding
is that

 1) we want the type to not have base because we may have inlined the constructor.
    During construction the vtables are filled by base's vtable and thus we can
    not simply devirtualize based on the final virtual table without proving that
    constructor was not (partially) inlined.

    This is ensured by the exisitng code refusing artifical types and
    by the check for number of bases being 0.
 2) apparently the code is supposed to refuse non-0 offset 
    because ipa-prop is tracking possibly dynamic type changes only for
    beggining of the objects.

What is wrong with 1
====================

I think 1) is unnecesarily restrictive.
  a) when tyhe bases of type found define no virtual methods (or do not
     define virtual method in question) then partially constructed types
     are not harmful.
  b) when the type found has a base type that defines given method, but do
     not overwrite it, we are safe too.

Less conservative solution for 1
================================

I think 1) can be also ignored in gimple_extract_devirt_binfo_from_cst pushing
the need to check for effects of partial construction into users.  I think I can
track constructors inlined into given function, but I am not doing it right now.
Intead I do two lookups - I find the method by the mechanizm above and I compare
it with the first definition of the method.  If they are same, I devirtualize.

What seems worng with 2
=======================

For 2) i actually found no code checking it. All I can notice that current
implementation will always fail to find anything in ipa-prop when
indirect_info->offset is non-0.  This is because the conditions implementing 1)
prevents gimple_extract_devirt_binfo_from_cst to return anything than the final
type and it will never match on any derived type (making the whole thing bit
pointless, since devirtualization is mostly about derived types I think).

Solution for 2
==============

It does not seem to break the testcases, but if check_stmt_for_type_change really
can not detect changes outside the main object, then I think all the code above
is wrong anyway and it needs to test for zero the combined offset (i.e. offset
given by get_ref_base_and_extent on the constant as well as offset of the
basetype given by indirect_info->offset).  This can be done easily and will
disable any transformations on derived types, but I suppose I can extend it
incrementally.

I am also confused why ipa-prop needs to track dynamic changes, while
gimple-fold is happy without doing so.  I do not know if one can do
something like having automatic variable of class A and use placement new
to change it to class B.  I tried to change devirt-6.C testcase to do so
but did not suceed in producing anything that would seem to have sane
construction and destruction order.  The local variable is destructed
at the end of sope block, so I would at least need to placement new it to
something class B, do somethjing and then placement new it back so the
destructor can be called.  I would hope this is invalid.  Is there any
sane summary of restriction of placement new use?

If such changing of dyanmic type is possible, we will need to track in
gimple-fold the changes, too.

My secret plan is to track these cases and devirtualize speculatively - the
dynamic type changes are rare and often they can be detected by the local
constant propagation on memory locations and optimized out anyway (at least
for the inlined constructors).

Why I can not use get_binfo_at_offset
=====================================

Finally I decided to replace the lookup of base binfo by call to
get_binfo_at_offset.  This is not possible.  For example my base
variable can be:

struct {class A a; class B b} var;

and offset 10 may point to class B.  Here I get an ICE since TYPE_BINFO
of the structure is NULL (it is not class). 

So we need to 
 A) walk type fields until we find an field we look for (containing the
    base defining virtual method in question)
 B) walk binfos of that field to find correct one.

A) is now done by new field walking in gimple_extract_devirt_binfo_from_cst
and then it dispatch to get_binfo_at_offset that implements B.

What is wrong with get_binfo_at_offset
======================================

From observation above I think get_binfo_at_offset code is wrong.  It has
code that attempts to walk non-artifical fields (i.e. non-base types).
It bails out on NULL BINFO that is likely to happen.  In fact get_binfo_at_offset
probably should use it through the loop in gimple_extract_devirt_binfo_from_cst
but I decided to leave this for incremental step.

Of course we can also handle array and union containers.  unions are bit
harder since we will need to collect all binfos and update callers to be ready
to receive multitude of them.

The proposed patch bootstraps&regtests x86_64-linux and ppc64-linux.

If it seems to make sense I will proceed with
 1) mentioned get_binfo_at_offset reorg
 2) using speculative call trick to handle cases where virtual method
    was overwritten
 3) trakcing of partial inlining of constructors to avoid need in speculation
    in most cases (I hope)
 4) I wonder if we can track functions that return pointers/values of objects
    exactly of given type (i.e. no derivations).  This seems to be implementable
    and also may allow to add user attribute returns_no_derived_types.
    The devirtualization machinery then can use the type knowledge as if it
    was the type of container var
 5) Is the dynamic type upon return from constructor always known to be of
    constructor's type?  We may want to introduce assert_type_expr use like:

    obj_ptr = assert_type_expr<class A> obj_ptr;

    that can be dropped by FE for us to drive those more interesting cases, like
    partial construction. Like for bulitin_expect we can drop this one after
    IPA passes.

Honza

	* ipa-cp.c (ipa_get_indirect_edge_target_1): Update use
	of gimple_extract_devirt_binfo_from_cst.
	* gimple-fold.c (gimple_extract_devirt_binfo_from_cst): Rework.
	(gimple_fold_call): Update use of gimple_extract_devirt_binfo_from_cst.
	* ipa-prop.c (try_make_edge_direct_virtual_call): Likewise.
	* gimple.h (gimple_extract_devirt_binfo_from_cst): Update.
Index: ipa-cp.c
===================================================================
--- ipa-cp.c	(revision 201814)
+++ ipa-cp.c	(working copy)
@@ -1541,14 +1541,24 @@  ipa_get_indirect_edge_target_1 (struct c
   if (TREE_CODE (t) != TREE_BINFO)
     {
       tree binfo;
+      tree target, base_target;
       binfo = gimple_extract_devirt_binfo_from_cst
-		 (t, ie->indirect_info->otr_type);
+		 (t, ie->indirect_info->otr_type,
+		  ie->indirect_info->offset);
       if (!binfo)
 	return NULL_TREE;
-      binfo = get_binfo_at_offset (binfo, anc_offset, otr_type);
-      if (!binfo)
+      target = gimple_get_virt_method_for_binfo (token, binfo);
+      if (!target)
+	return NULL_TREE;
+      /* Constructors may be partially inlined.  We do not track
+         if type is in construction and during that time the
+	 virtual table may correspond to virtual table of the
+	 base type.  */
+      base_target = gimple_get_virt_method_for_binfo
+		     (token, TYPE_BINFO (ie->indirect_info->otr_type));
+      if (base_target != target)
 	return NULL_TREE;
-      return gimple_get_virt_method_for_binfo (token, binfo);
+      return target;
     }
   else
     {
Index: gimple-fold.c
===================================================================
--- gimple-fold.c	(revision 201814)
+++ gimple-fold.c	(working copy)
@@ -1006,16 +1006,20 @@  gimple_fold_builtin (gimple stmt)
 /* Return a binfo to be used for devirtualization of calls based on an object
    represented by a declaration (i.e. a global or automatically allocated one)
    or NULL if it cannot be found or is not safe.  CST is expected to be an
-   ADDR_EXPR of such object or the function will return NULL.  Currently it is
-   safe to use such binfo only if it has no base binfo (i.e. no ancestors)
-   EXPECTED_TYPE is type of the class virtual belongs to.  */
+   ADDR_EXPR of such object or the function will return NULL.
+
+   It is up to the caller to check for absence of dynamic type changes.
+   Because constructors may be partially inlined and the virtual tables
+   during construction may be overwritten by virtual tables by base types,
+   it is also up to caller to verify that either all base types have
+   the same virtual method or that this does not happen.  */
 
 tree
-gimple_extract_devirt_binfo_from_cst (tree cst, tree expected_type)
+gimple_extract_devirt_binfo_from_cst (tree cst, tree expected_type,
+				      HOST_WIDE_INT otr_offset)
 {
-  HOST_WIDE_INT offset, size, max_size;
-  tree base, type, binfo;
-  bool last_artificial = false;
+  HOST_WIDE_INT offset, size, max_size, base_offset;
+  tree base, type, last_nonartifical;
 
   if (!flag_devirtualize
       || TREE_CODE (cst) != ADDR_EXPR
@@ -1024,15 +1028,19 @@  gimple_extract_devirt_binfo_from_cst (tr
 
   cst = TREE_OPERAND (cst, 0);
   base = get_ref_base_and_extent (cst, &offset, &size, &max_size);
+  base_offset = offset;
   type = TREE_TYPE (base);
+  last_nonartifical = type;
   if (!DECL_P (base)
       || max_size == -1
       || max_size != size
       || TREE_CODE (type) != RECORD_TYPE)
     return NULL_TREE;
 
-  /* Find the sub-object the constant actually refers to and mark whether it is
-     an artificial one (as opposed to a user-defined one).  */
+  /* Find the sub-object the constant actually refers to.
+     We are interested in the innermost non-artifical one, since that
+     actually represent the basetype of the field.
+     Rest of walking is done by get_binfo_at_offset.  */
   while (true)
     {
       HOST_WIDE_INT pos, size;
@@ -1056,19 +1064,19 @@  gimple_extract_devirt_binfo_from_cst (tr
       if (!fld || TREE_CODE (TREE_TYPE (fld)) != RECORD_TYPE)
 	return NULL_TREE;
 
-      last_artificial = DECL_ARTIFICIAL (fld);
       type = TREE_TYPE (fld);
+      if (!DECL_ARTIFICIAL (fld))
+	{
+	  last_nonartifical = type;
+	  base_offset = offset;
+	}
       offset -= pos;
     }
-  /* Artificial sub-objects are ancestors, we do not want to use them for
-     devirtualization, at least not here.  */
-  if (last_artificial)
-    return NULL_TREE;
-  binfo = TYPE_BINFO (type);
-  if (!binfo || BINFO_N_BASE_BINFOS (binfo) > 0)
+
+  if (!TYPE_BINFO (last_nonartifical))
     return NULL_TREE;
-  else
-    return binfo;
+  return get_binfo_at_offset (TYPE_BINFO (last_nonartifical),
+			      base_offset + otr_offset, expected_type);
 }
 
 /* Attempt to fold a call statement referenced by the statement iterator GSI.
@@ -1108,14 +1116,20 @@  gimple_fold_call (gimple_stmt_iterator *
       else
 	{
 	  tree obj = OBJ_TYPE_REF_OBJECT (callee);
+	  tree class_type = obj_type_ref_class (callee);
 	  tree binfo = gimple_extract_devirt_binfo_from_cst
-		 (obj, obj_type_ref_class (callee));
+		 (obj, class_type, 0);
 	  if (binfo)
 	    {
 	      HOST_WIDE_INT token
 		= TREE_INT_CST_LOW (OBJ_TYPE_REF_TOKEN (callee));
 	      tree fndecl = gimple_get_virt_method_for_binfo (token, binfo);
-	      if (fndecl)
+	      tree base_fndecl = gimple_get_virt_method_for_binfo (token, TYPE_BINFO (class_type));
+	      /* Constructors may be partially inlined.  We do not track
+		 if type is in construction and during that time the
+		 virtual table may correspond to virtual table of the
+		 base type.  */
+	      if (fndecl && base_fndecl == fndecl)
 		{
 		  gimple_call_set_fndecl (stmt, fndecl);
 		  changed = true;
Index: ipa-prop.c
===================================================================
--- ipa-prop.c	(revision 201814)
+++ ipa-prop.c	(working copy)
@@ -2444,7 +2444,7 @@  try_make_edge_direct_virtual_call (struc
 				   struct ipa_jump_func *jfunc,
 				   struct ipa_node_params *new_root_info)
 {
-  tree binfo, target;
+  tree binfo, target, base_target = NULL;
 
   binfo = ipa_value_from_jfunc (new_root_info, jfunc);
 
@@ -2454,19 +2454,31 @@  try_make_edge_direct_virtual_call (struc
   if (TREE_CODE (binfo) != TREE_BINFO)
     {
       binfo = gimple_extract_devirt_binfo_from_cst
-		 (binfo, ie->indirect_info->otr_type);
+		 (binfo, ie->indirect_info->otr_type,
+		  ie->indirect_info->offset);
       if (!binfo)
         return NULL;
+      /* Constructors may be partially inlined.  We do not track
+         if type is in construction and during that time the
+	 virtual table may correspond to virtual table of the
+	 base type.  */
+      base_target = gimple_get_virt_method_for_binfo (ie->indirect_info->otr_token,
+						      TYPE_BINFO (ie->indirect_info->otr_type));
+      if (!base_target)
+	return NULL;
     }
-
-  binfo = get_binfo_at_offset (binfo, ie->indirect_info->offset,
-			       ie->indirect_info->otr_type);
+  else
+    binfo = get_binfo_at_offset (binfo, ie->indirect_info->offset,
+				 ie->indirect_info->otr_type);
   if (binfo)
     target = gimple_get_virt_method_for_binfo (ie->indirect_info->otr_token,
 					       binfo);
   else
     return NULL;
 
+  if (base_target && target != base_target)
+    return NULL;
+
   if (target)
     return ipa_make_edge_direct_to_target (ie, target);
   else
Index: gimple.h
===================================================================
--- gimple.h	(revision 201814)
+++ gimple.h	(working copy)
@@ -854,7 +854,7 @@  unsigned get_gimple_rhs_num_ops (enum tr
 gimple gimple_alloc_stat (enum gimple_code, unsigned MEM_STAT_DECL);
 const char *gimple_decl_printable_name (tree, int);
 tree gimple_get_virt_method_for_binfo (HOST_WIDE_INT, tree);
-tree gimple_extract_devirt_binfo_from_cst (tree, tree);
+tree gimple_extract_devirt_binfo_from_cst (tree, tree, HOST_WIDE_INT);
 
 /* Returns true iff T is a scalar register variable.  */
 extern bool is_gimple_reg (tree);