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
===================================

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);

Reply via email to