Comments

This is a re-submission of the patch originally posted here:
http://gcc.gnu.org/ml/gcc-patches/2010-12/msg00091.html
The main differences are:
1. No special provisons for builtin functions. There is a new big
comment explaining why we ignore all calls when searching for
statements changing the dynamic type.
2. Aggregate stores to COMPONENT_REFs are also considered as changing
dynamic the type.
3. The new use of DECL_CONTEXT is briefly documented in tree.h.
4. check_stmt_for_type_change is now ready to be called even after it
has returned true and checks that if we derive any known new
dynamic type, it is always the same one.
5. I have renamed the functions a slightly. There is now a function
called detect_type_change which should be directly used for
declarations and their component_refs and dereferenced pointers and
their component_refs. Dereferenced pointers are not supplied
directly from the analyzed IL but are created by
detect_type_change_ssa which is to be used to detect any dynamic
type changed of objects pointed to by an SSA_NAME (as it currently
is, all callers check a PARAM_DECL DEFAULT_DEF
SSA_NAMEs). detect_type_change_ssa uses detect_type_change
internally.
So when we construct a PASS_THROUGH jump function without any
binary operation (pass through functions with an operation are
never combined with a known_type ones), we use
detect_type_change_ssa.
On the other hand, when we either want to construct an ANCESTOR
jump function (and therefore are looking at an address of a
component of a dereferenced pointer) or TYPE_KNOWN jump function
(looking either at an address of a decl or its component), we use
directly detect_type_change. On both occasions we already need to
invoke get_ref_base_and extent and so we supply this information to
detect_type_change which uses it to initialize its ao_ref structure
on its own.
I was considering using ao_ref_init_from_ptr_and_size but I would
then need to adjust ref_alias_set and base_alias_set because I
really want to utilize TBAA (and therefore either compute the sets
or set the ref field too) and considered that to be even uglier
than setting up the whole thing myself.
The size is set to the size of a pointer. We really only care
about components representing ancestors (get_binfo_at_offset is
smart enough to recognize them and not use encapsulating
object-type for non-artificial components) which have at least one
VMT pointer right at the beginning if they have any virtual methods
at all and cannot be part of arrays or larger structures without a
VMT at the beginning. This also prevents us from wrongly deriving
a type from an object constructed inside another one with placement
new (there is a new testcase in the next patch for this).
All of this is for objects passed by reference, there is no
provision (or intention) to track stuff passed by value.
I hope I answered all comments raised in the previous thread.
As before, the code that does both detection of dynamic type changes
and tries to extract the new dynamic type in some cases is the
following:
----------------------------------------------------------------------
/* Structure to be passed in between detect_type_change and
check_stmt_for_type_change. */
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.
This function makes special assumptions about both constructors and
destructors which are all the functions that are allowed to alter the VMT
pointers. It assumes that destructors begin with assignment into all VMT
pointers and that constructors essentially look in the following way:
1) The very first thing they do is that they call constructors of ancestor
sub-objects that have them.
2) Then VMT pointers of this and all its ancestors is set to new values
corresponding to the type corresponding to the constructor.
3) Only afterwards, other stuff such as constructor of member sub-objects
and the code written by the user is run. Only this may include calling
virtual functions, directly or indirectly.
There is no way to call a constructor of an ancestor sub-object in any
other way.
This means that we do not have to care whether constructors get the correct
type information because they will always change it (in fact, if we define
the type to be given by the VMT pointer, it is undefined).
The most important fact to derive from the above is that if, for some
statement in the section 3, we try to detect whether the dynamic type has
changed, we can safely ignore all calls as we examine the function body
backwards until we reach statements in section 2 because these calls cannot
be ancestor constructors or destructors (if the input is not bogus) and so
do not change the dynamic type. We then must detect that statements in
section 2 change the dynamic type and can try to derive the new type. That
is enough and we can stop, we will never see the calls into constructors of
sub-objects in this code. Therefore we can safely ignore all call
statements that we traverse.
*/
static bool
stmt_may_be_vtbl_ptr_store (gimple stmt)
{
if (is_gimple_call (stmt))
return false;
else if (is_gimple_assign (stmt))
{
tree lhs = gimple_assign_lhs (stmt);
if (TREE_CODE (lhs) == COMPONENT_REF
&& !DECL_VIRTUAL_P (TREE_OPERAND (lhs, 1))
&& !AGGREGATE_TYPE_P (TREE_TYPE (lhs)))
return false;
/* In the future we might want to use get_base_ref_and_offset to find
if there is a field corresponding to the offset and if so, proceed
almost like if it was a component ref. */
}
return true;
}
/* If STMT can be proved to be an assignment to the virtual method table
pointer of ANALYZED_OBJ and the type associated witht 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))
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);
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));
if (!t || TREE_CODE (t) != VAR_DECL || !DECL_VIRTUAL_P (t))
return NULL_TREE;
return DECL_CONTEXT (t);
}
/* Callbeck 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
the (sub-)object. It stores its result into DATA, which points to a
type_change_info structure. */
static bool
check_stmt_for_type_change (ao_ref *ao ATTRIBUTE_UNUSED, tree vdef, void *data)
{
gimple stmt = SSA_NAME_DEF_STMT (vdef);
struct type_change_info *tci = (struct type_change_info *) data;
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;
}
else
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. */
static bool
detect_type_change (tree arg, tree base, gimple call,
struct ipa_jump_func *jfunc, HOST_WIDE_INT offset)
{
struct type_change_info tci;
tree type;
ao_ref ao;
gcc_checking_assert (DECL_P (arg)
|| TREE_CODE (arg) == MEM_REF
|| handled_component_p (arg));
/* Const calls cannot call virtual methods through VMT and so type changes do
not matter. */
if (!gimple_vuse (call))
return false;
ao.ref = arg;
ao.base = base;
ao.offset = offset;
ao.size = TREE_INT_CST_LOW (TYPE_SIZE_UNIT (ptr_type_node)) * BITS_PER_UNIT;
ao.max_size = ao.size;
ao.ref_alias_set = -1;
ao.base_alias_set = -1;
type = TREE_TYPE (arg);
while (handled_component_p (arg))
arg = TREE_OPERAND (arg, 0);
if (TREE_CODE (arg) == MEM_REF)
arg = TREE_OPERAND (arg, 0);
if (TREE_CODE (arg) == ADDR_EXPR)
arg = TREE_OPERAND (arg, 0);
tci.object = 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;
if (!tci.known_current_type || tci.multiple_types_encountered)
jfunc->type = IPA_JF_UNKNOWN;
else
{
tree new_binfo = get_binfo_at_offset (TYPE_BINFO (tci.known_current_type),
offset, type);
if (new_binfo)
{
jfunc->type = IPA_JF_KNOWN_TYPE;
jfunc->value.base_binfo = new_binfo;
}
else
jfunc->type = IPA_JF_UNKNOWN;
}
return true;
}
/* 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). */
static bool
detect_type_change_ssa (tree arg, gimple call, struct ipa_jump_func *jfunc)
{
gcc_checking_assert (TREE_CODE (arg) == SSA_NAME);
if (!POINTER_TYPE_P (TREE_TYPE (arg))
|| TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) != RECORD_TYPE)
return false;
arg = build_simple_mem_ref (arg);
return detect_type_change (arg, arg, call, jfunc, 0);
}
----------------------------------------------------------------------
The patch implementing just the detection of changes with all the
callers is below, the rest is in the next patch.
I have bootstrapped and tested this patch separately (i.e. with the
previous ones but not the subsequent ones) on x86-64-linux and it has
also passed make check-c++ on i686.
Thanks for any comments,
Martin
2010-11-09 Martin Jambor <mjambor@suse.cz>
PR tree-optimization/45934
PR tree-optimization/46302
* ipa-prop.c (type_change_info): New type.
(stmt_may_be_vtbl_ptr_store): New function.
(check_stmt_for_type_change): Likewise.
(detect_type_change): Likewise.
(detect_type_change_ssa): Likewise.
(compute_complex_assign_jump_func): Check for dynamic type change.
(compute_complex_ancestor_jump_func): Likewise.
(compute_known_type_jump_func): Likewise.
(compute_scalar_jump_functions): Likewise.
(ipa_analyze_virtual_call_uses): Likewise.
(ipa_analyze_node): Push and pop cfun, set current_function_decl.
* testsuite/g++.dg/ipa/devirt-c-1.C: New test.
* testsuite/g++.dg/ipa/devirt-c-2.C: Likewise.
* testsuite/g++.dg/ipa/devirt-c-3.C: Likewise.
* testsuite/g++.dg/ipa/devirt-c-4.C: Likewise.
* testsuite/g++.dg/ipa/devirt-c-5.C: Likewise.
* testsuite/g++.dg/ipa/devirt-c-6.C: Likewise.
* testsuite/g++.dg/ipa/devirt-c-7.C: Likewise.
* testsuite/g++.dg/ipa/devirt-d-1.C: Likewise.
* testsuite/g++.dg/torture/pr45934.C: Likewise.