ipa-devirt.c: Include gimple-pretty-print.h
* ipa-devirt.c: Include gimple-pretty-print.h (referenced_from_vtable_p): Exclude DECL_EXTERNAL from further tests. (decl_maybe_in_construction_p): Fix conditional on cdtor check (get_polymorphic_call_info): Fix return value (type_change_info): New sturcture based on ipa-prop variant. (noncall_stmt_may_be_vtbl_ptr_store): New predicate based on ipa-prop variant. (extr_type_from_vtbl_ptr_store): New function based on ipa-prop variant. (record_known_type): New function. (check_stmt_for_type_change): New function. (get_dynamic_type): New function. * ipa-prop.c (ipa_analyze_call_uses): Use get_dynamic_type. * tree-ssa-pre.c: ipa-utils.h (eliminate_dom_walker::before_dom_children): Use ipa-devirt machinery; sanity check with ipa-prop devirtualization. * trans-mem.c (ipa_tm_insert_gettmclone_call): Clear polymorphic flag. * g++.dg/ipa/devirt-35.C: New testcase. * g++.dg/ipa/devirt-36.C: New testcase. From-SVN: r213739
This commit is contained in:
parent
9f25a338f9
commit
7d0aa05b9e
@ -1,3 +1,26 @@
|
|||||||
|
2014-08-07 Jan Hubicka <hubicka@ucw.cz>
|
||||||
|
|
||||||
|
* ipa-devirt.c: Include gimple-pretty-print.h
|
||||||
|
(referenced_from_vtable_p): Exclude DECL_EXTERNAL from
|
||||||
|
further tests.
|
||||||
|
(decl_maybe_in_construction_p): Fix conditional on cdtor check
|
||||||
|
(get_polymorphic_call_info): Fix return value
|
||||||
|
(type_change_info): New sturcture based on ipa-prop
|
||||||
|
variant.
|
||||||
|
(noncall_stmt_may_be_vtbl_ptr_store): New predicate
|
||||||
|
based on ipa-prop variant.
|
||||||
|
(extr_type_from_vtbl_ptr_store): New function
|
||||||
|
based on ipa-prop variant.
|
||||||
|
(record_known_type): New function.
|
||||||
|
(check_stmt_for_type_change): New function.
|
||||||
|
(get_dynamic_type): New function.
|
||||||
|
* ipa-prop.c (ipa_analyze_call_uses): Use get_dynamic_type.
|
||||||
|
* tree-ssa-pre.c: ipa-utils.h
|
||||||
|
(eliminate_dom_walker::before_dom_children): Use ipa-devirt
|
||||||
|
machinery; sanity check with ipa-prop devirtualization.
|
||||||
|
* trans-mem.c (ipa_tm_insert_gettmclone_call): Clear
|
||||||
|
polymorphic flag.
|
||||||
|
|
||||||
2014-08-07 Trevor Saunders <tsaunders@mozilla.com>
|
2014-08-07 Trevor Saunders <tsaunders@mozilla.com>
|
||||||
|
|
||||||
* Makefile.in: Remove references to pointer-set.c and pointer-set.h.
|
* Makefile.in: Remove references to pointer-set.c and pointer-set.h.
|
||||||
|
527
gcc/ipa-devirt.c
527
gcc/ipa-devirt.c
@ -131,6 +131,7 @@ along with GCC; see the file COPYING3. If not see
|
|||||||
#include "tree-dfa.h"
|
#include "tree-dfa.h"
|
||||||
#include "demangle.h"
|
#include "demangle.h"
|
||||||
#include "dbgcnt.h"
|
#include "dbgcnt.h"
|
||||||
|
#include "gimple-pretty-print.h"
|
||||||
#include "stor-layout.h"
|
#include "stor-layout.h"
|
||||||
#include "intl.h"
|
#include "intl.h"
|
||||||
#include "hash-map.h"
|
#include "hash-map.h"
|
||||||
@ -1323,6 +1324,7 @@ referenced_from_vtable_p (struct cgraph_node *node)
|
|||||||
bool found = false;
|
bool found = false;
|
||||||
|
|
||||||
if (node->externally_visible
|
if (node->externally_visible
|
||||||
|
|| DECL_EXTERNAL (node->decl)
|
||||||
|| node->used_from_other_partition)
|
|| node->used_from_other_partition)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -2113,7 +2115,7 @@ decl_maybe_in_construction_p (tree base, tree outer_type,
|
|||||||
|
|
||||||
if (TREE_CODE (TREE_TYPE (fn)) != METHOD_TYPE
|
if (TREE_CODE (TREE_TYPE (fn)) != METHOD_TYPE
|
||||||
|| (!DECL_CXX_CONSTRUCTOR_P (fn)
|
|| (!DECL_CXX_CONSTRUCTOR_P (fn)
|
||||||
|| !DECL_CXX_DESTRUCTOR_P (fn)))
|
&& !DECL_CXX_DESTRUCTOR_P (fn)))
|
||||||
{
|
{
|
||||||
/* Watch for clones where we constant propagated the first
|
/* Watch for clones where we constant propagated the first
|
||||||
argument (pointer to the instance). */
|
argument (pointer to the instance). */
|
||||||
@ -2122,7 +2124,7 @@ decl_maybe_in_construction_p (tree base, tree outer_type,
|
|||||||
|| !is_global_var (base)
|
|| !is_global_var (base)
|
||||||
|| TREE_CODE (TREE_TYPE (fn)) != METHOD_TYPE
|
|| TREE_CODE (TREE_TYPE (fn)) != METHOD_TYPE
|
||||||
|| (!DECL_CXX_CONSTRUCTOR_P (fn)
|
|| (!DECL_CXX_CONSTRUCTOR_P (fn)
|
||||||
|| !DECL_CXX_DESTRUCTOR_P (fn)))
|
&& !DECL_CXX_DESTRUCTOR_P (fn)))
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (flags_from_decl_or_type (fn) & (ECF_PURE | ECF_CONST))
|
if (flags_from_decl_or_type (fn) & (ECF_PURE | ECF_CONST))
|
||||||
@ -2142,7 +2144,7 @@ decl_maybe_in_construction_p (tree base, tree outer_type,
|
|||||||
{
|
{
|
||||||
if (TREE_CODE (TREE_TYPE (function)) != METHOD_TYPE
|
if (TREE_CODE (TREE_TYPE (function)) != METHOD_TYPE
|
||||||
|| (!DECL_CXX_CONSTRUCTOR_P (function)
|
|| (!DECL_CXX_CONSTRUCTOR_P (function)
|
||||||
|| !DECL_CXX_DESTRUCTOR_P (function)))
|
&& !DECL_CXX_DESTRUCTOR_P (function)))
|
||||||
{
|
{
|
||||||
if (!DECL_ABSTRACT_ORIGIN (function))
|
if (!DECL_ABSTRACT_ORIGIN (function))
|
||||||
return false;
|
return false;
|
||||||
@ -2152,7 +2154,7 @@ decl_maybe_in_construction_p (tree base, tree outer_type,
|
|||||||
if (!function
|
if (!function
|
||||||
|| TREE_CODE (TREE_TYPE (function)) != METHOD_TYPE
|
|| TREE_CODE (TREE_TYPE (function)) != METHOD_TYPE
|
||||||
|| (!DECL_CXX_CONSTRUCTOR_P (function)
|
|| (!DECL_CXX_CONSTRUCTOR_P (function)
|
||||||
|| !DECL_CXX_DESTRUCTOR_P (function)))
|
&& !DECL_CXX_DESTRUCTOR_P (function)))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
/* FIXME: this can go away once we have ODR types equivalency on
|
/* FIXME: this can go away once we have ODR types equivalency on
|
||||||
@ -2243,7 +2245,8 @@ walk_ssa_copies (tree op)
|
|||||||
call (OTR_TYPE), its token (OTR_TOKEN) and CONTEXT.
|
call (OTR_TYPE), its token (OTR_TOKEN) and CONTEXT.
|
||||||
CALL is optional argument giving the actual statement (usually call) where
|
CALL is optional argument giving the actual statement (usually call) where
|
||||||
the context is used.
|
the context is used.
|
||||||
Return pointer to object described by the context */
|
Return pointer to object described by the context or an declaration if
|
||||||
|
we found the instance to be stored in the static storage. */
|
||||||
|
|
||||||
tree
|
tree
|
||||||
get_polymorphic_call_info (tree fndecl,
|
get_polymorphic_call_info (tree fndecl,
|
||||||
@ -2317,7 +2320,7 @@ get_polymorphic_call_info (tree fndecl,
|
|||||||
context->outer_type,
|
context->outer_type,
|
||||||
call,
|
call,
|
||||||
current_function_decl);
|
current_function_decl);
|
||||||
return base_pointer;
|
return base;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
@ -2436,6 +2439,515 @@ get_polymorphic_call_info (tree fndecl,
|
|||||||
return base_pointer;
|
return base_pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Structure to be passed in between detect_type_change and
|
||||||
|
check_stmt_for_type_change. */
|
||||||
|
|
||||||
|
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 instance;
|
||||||
|
/* The reference to virtual table pointer used. */
|
||||||
|
tree vtbl_ptr_ref;
|
||||||
|
tree otr_type;
|
||||||
|
/* 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;
|
||||||
|
HOST_WIDE_INT known_current_offset;
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
/* Set to true if we possibly missed some dynamic type changes and we should
|
||||||
|
consider the set to be speculative. */
|
||||||
|
bool speculative;
|
||||||
|
bool seen_unanalyzed_store;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Return true if STMT is not call and can modify a virtual method table pointer.
|
||||||
|
We take advantage of fact that vtable stores must appear within constructor
|
||||||
|
and destructor functions. */
|
||||||
|
|
||||||
|
bool
|
||||||
|
noncall_stmt_may_be_vtbl_ptr_store (gimple stmt)
|
||||||
|
{
|
||||||
|
if (is_gimple_assign (stmt))
|
||||||
|
{
|
||||||
|
tree lhs = gimple_assign_lhs (stmt);
|
||||||
|
|
||||||
|
if (gimple_clobber_p (stmt))
|
||||||
|
return false;
|
||||||
|
if (!AGGREGATE_TYPE_P (TREE_TYPE (lhs)))
|
||||||
|
{
|
||||||
|
if (flag_strict_aliasing
|
||||||
|
&& !POINTER_TYPE_P (TREE_TYPE (lhs)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (TREE_CODE (lhs) == COMPONENT_REF
|
||||||
|
&& !DECL_VIRTUAL_P (TREE_OPERAND (lhs, 1)))
|
||||||
|
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. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code unification may mess with inline stacks. */
|
||||||
|
if (cfun->after_inlining)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/* Walk the inline stack and watch out for ctors/dtors.
|
||||||
|
TODO: Maybe we can require the store to appear in toplevel
|
||||||
|
block of CTOR/DTOR. */
|
||||||
|
for (tree block = gimple_block (stmt); block && TREE_CODE (block) == BLOCK;
|
||||||
|
block = BLOCK_SUPERCONTEXT (block))
|
||||||
|
if (BLOCK_ABSTRACT_ORIGIN (block)
|
||||||
|
&& TREE_CODE (BLOCK_ABSTRACT_ORIGIN (block)) == FUNCTION_DECL)
|
||||||
|
{
|
||||||
|
tree fn = BLOCK_ABSTRACT_ORIGIN (block);
|
||||||
|
|
||||||
|
if (flags_from_decl_or_type (fn) & (ECF_PURE | ECF_CONST))
|
||||||
|
return false;
|
||||||
|
return (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE
|
||||||
|
&& (DECL_CXX_CONSTRUCTOR_P (fn)
|
||||||
|
|| DECL_CXX_DESTRUCTOR_P (fn)));
|
||||||
|
}
|
||||||
|
return (TREE_CODE (TREE_TYPE (current_function_decl)) == METHOD_TYPE
|
||||||
|
&& (DECL_CXX_CONSTRUCTOR_P (current_function_decl)
|
||||||
|
|| DECL_CXX_DESTRUCTOR_P (current_function_decl)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 *type_offset)
|
||||||
|
{
|
||||||
|
HOST_WIDE_INT offset, size, max_size;
|
||||||
|
tree lhs, rhs, base, binfo;
|
||||||
|
|
||||||
|
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)))
|
||||||
|
return NULL_TREE;
|
||||||
|
|
||||||
|
if (tci->vtbl_ptr_ref && operand_equal_p (lhs, tci->vtbl_ptr_ref, 0))
|
||||||
|
;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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 (DECL_P (tci->instance))
|
||||||
|
{
|
||||||
|
if (base != tci->instance)
|
||||||
|
return NULL_TREE;
|
||||||
|
}
|
||||||
|
else if (TREE_CODE (base) == MEM_REF)
|
||||||
|
{
|
||||||
|
if (!operand_equal_p (tci->instance, TREE_OPERAND (base, 0), 0)
|
||||||
|
|| !integer_zerop (TREE_OPERAND (base, 1)))
|
||||||
|
return NULL_TREE;
|
||||||
|
}
|
||||||
|
else if (!operand_equal_p (tci->instance, base, 0)
|
||||||
|
|| tci->offset)
|
||||||
|
return NULL_TREE;
|
||||||
|
}
|
||||||
|
|
||||||
|
binfo = vtable_pointer_value_to_binfo (rhs);
|
||||||
|
|
||||||
|
if (!binfo)
|
||||||
|
return NULL;
|
||||||
|
*type_offset = tree_to_shwi (BINFO_OFFSET (binfo)) * BITS_PER_UNIT;
|
||||||
|
if (TYPE_BINFO (BINFO_TYPE (binfo)) == binfo)
|
||||||
|
return BINFO_TYPE (binfo);
|
||||||
|
|
||||||
|
/* TODO: Figure out the type containing BINFO. */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Record dynamic type change of TCI to TYPE. */
|
||||||
|
|
||||||
|
void
|
||||||
|
record_known_type (struct type_change_info *tci, tree type, HOST_WIDE_INT offset)
|
||||||
|
{
|
||||||
|
if (dump_file)
|
||||||
|
{
|
||||||
|
if (type)
|
||||||
|
{
|
||||||
|
fprintf (dump_file, " Recording type: ");
|
||||||
|
print_generic_expr (dump_file, type, TDF_SLIM);
|
||||||
|
fprintf (dump_file, " at offset %i\n", (int)offset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fprintf (dump_file, " Recording unknown type\n");
|
||||||
|
}
|
||||||
|
if (tci->type_maybe_changed
|
||||||
|
&& (type != tci->known_current_type
|
||||||
|
|| offset != tci->known_current_offset))
|
||||||
|
tci->multiple_types_encountered = true;
|
||||||
|
tci->known_current_type = type;
|
||||||
|
tci->known_current_offset = offset;
|
||||||
|
tci->type_maybe_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
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;
|
||||||
|
tree fn;
|
||||||
|
|
||||||
|
/* If we already gave up, just terminate the rest of walk. */
|
||||||
|
if (tci->multiple_types_encountered)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (is_gimple_call (stmt))
|
||||||
|
{
|
||||||
|
if (gimple_call_flags (stmt) & (ECF_CONST | ECF_PURE))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Check for a constructor call. */
|
||||||
|
if ((fn = gimple_call_fndecl (stmt)) != NULL_TREE
|
||||||
|
&& DECL_CXX_CONSTRUCTOR_P (fn)
|
||||||
|
&& TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE
|
||||||
|
&& gimple_call_num_args (stmt))
|
||||||
|
{
|
||||||
|
tree op = walk_ssa_copies (gimple_call_arg (stmt, 0));
|
||||||
|
tree type = method_class_type (TREE_TYPE (fn));
|
||||||
|
HOST_WIDE_INT offset = 0, size, max_size;
|
||||||
|
|
||||||
|
if (dump_file)
|
||||||
|
{
|
||||||
|
fprintf (dump_file, " Checking constructor call: ");
|
||||||
|
print_gimple_stmt (dump_file, stmt, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See if THIS parameter seems like instance pointer. */
|
||||||
|
if (TREE_CODE (op) == ADDR_EXPR)
|
||||||
|
{
|
||||||
|
op = get_ref_base_and_extent (TREE_OPERAND (op, 0),
|
||||||
|
&offset, &size, &max_size);
|
||||||
|
if (size != max_size || max_size == -1)
|
||||||
|
{
|
||||||
|
tci->speculative = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (op && TREE_CODE (op) == MEM_REF)
|
||||||
|
{
|
||||||
|
if (!tree_fits_shwi_p (TREE_OPERAND (op, 1)))
|
||||||
|
{
|
||||||
|
tci->speculative = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
offset += tree_to_shwi (TREE_OPERAND (op, 1))
|
||||||
|
* BITS_PER_UNIT;
|
||||||
|
op = TREE_OPERAND (op, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tci->speculative = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
op = walk_ssa_copies (op);
|
||||||
|
}
|
||||||
|
if (operand_equal_p (op, tci->instance, 0)
|
||||||
|
&& TYPE_SIZE (type)
|
||||||
|
&& TREE_CODE (TYPE_SIZE (type)) == INTEGER_CST
|
||||||
|
&& tree_fits_shwi_p (TYPE_SIZE (type))
|
||||||
|
&& tree_to_shwi (TYPE_SIZE (type)) + offset > tci->offset)
|
||||||
|
{
|
||||||
|
record_known_type (tci, type, tci->offset - offset);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Calls may possibly change dynamic type by placement new. Assume
|
||||||
|
it will not happen, but make result speculative only. */
|
||||||
|
if (dump_file)
|
||||||
|
{
|
||||||
|
fprintf (dump_file, " Function call may change dynamic type:");
|
||||||
|
print_gimple_stmt (dump_file, stmt, 0, 0);
|
||||||
|
}
|
||||||
|
tci->speculative = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/* Check for inlined virtual table store. */
|
||||||
|
else if (noncall_stmt_may_be_vtbl_ptr_store (stmt))
|
||||||
|
{
|
||||||
|
tree type;
|
||||||
|
HOST_WIDE_INT offset = 0;
|
||||||
|
if (dump_file)
|
||||||
|
{
|
||||||
|
fprintf (dump_file, " Checking vtbl store: ");
|
||||||
|
print_gimple_stmt (dump_file, stmt, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
type = extr_type_from_vtbl_ptr_store (stmt, tci, &offset);
|
||||||
|
gcc_assert (!type || TYPE_MAIN_VARIANT (type) == type);
|
||||||
|
if (!type)
|
||||||
|
{
|
||||||
|
if (dump_file)
|
||||||
|
fprintf (dump_file, " Unanalyzed store may change type.\n");
|
||||||
|
tci->seen_unanalyzed_store = true;
|
||||||
|
tci->speculative = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
record_known_type (tci, type, offset);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CONTEXT is polymorphic call context obtained from get_polymorphic_context.
|
||||||
|
OTR_OBJECT is pointer to the instance returned by OBJ_TYPE_REF_OBJECT.
|
||||||
|
INSTANCE is pointer to the outer instance as returned by
|
||||||
|
get_polymorphic_context. To avoid creation of temporary expressions,
|
||||||
|
INSTANCE may also be an declaration of get_polymorphic_context found the
|
||||||
|
value to be in static storage.
|
||||||
|
|
||||||
|
If the type of instance is not fully determined
|
||||||
|
(either OUTER_TYPE is unknown or MAYBE_IN_CONSTRUCTION/INCLUDE_DERIVED_TYPES
|
||||||
|
is set), try to walk memory writes and find the actual construction of the
|
||||||
|
instance.
|
||||||
|
|
||||||
|
We do not include this analysis in the context analysis itself, because
|
||||||
|
it needs memory SSA to be fully built and the walk may be expensive.
|
||||||
|
So it is not suitable for use withing fold_stmt and similar uses. */
|
||||||
|
|
||||||
|
bool
|
||||||
|
get_dynamic_type (tree instance,
|
||||||
|
ipa_polymorphic_call_context *context,
|
||||||
|
tree otr_object,
|
||||||
|
tree otr_type,
|
||||||
|
gimple call)
|
||||||
|
{
|
||||||
|
struct type_change_info tci;
|
||||||
|
ao_ref ao;
|
||||||
|
bool function_entry_reached = false;
|
||||||
|
tree instance_ref = NULL;
|
||||||
|
gimple stmt = call;
|
||||||
|
|
||||||
|
if (!context->maybe_in_construction && !context->maybe_derived_type)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* We need to obtain refernce to virtual table pointer. It is better
|
||||||
|
to look it up in the code rather than build our own. This require bit
|
||||||
|
of pattern matching, but we end up verifying that what we found is
|
||||||
|
correct.
|
||||||
|
|
||||||
|
What we pattern match is:
|
||||||
|
|
||||||
|
tmp = instance->_vptr.A; // vtbl ptr load
|
||||||
|
tmp2 = tmp[otr_token]; // vtable lookup
|
||||||
|
OBJ_TYPE_REF(tmp2;instance->0) (instance);
|
||||||
|
|
||||||
|
We want to start alias oracle walk from vtbl pointer load,
|
||||||
|
but we may not be able to identify it, for example, when PRE moved the
|
||||||
|
load around. */
|
||||||
|
|
||||||
|
if (gimple_code (call) == GIMPLE_CALL)
|
||||||
|
{
|
||||||
|
tree ref = gimple_call_fn (call);
|
||||||
|
HOST_WIDE_INT offset2, size, max_size;
|
||||||
|
|
||||||
|
if (TREE_CODE (ref) == OBJ_TYPE_REF)
|
||||||
|
{
|
||||||
|
ref = OBJ_TYPE_REF_EXPR (ref);
|
||||||
|
ref = walk_ssa_copies (ref);
|
||||||
|
|
||||||
|
/* Check if definition looks like vtable lookup. */
|
||||||
|
if (TREE_CODE (ref) == SSA_NAME
|
||||||
|
&& !SSA_NAME_IS_DEFAULT_DEF (ref)
|
||||||
|
&& gimple_assign_load_p (SSA_NAME_DEF_STMT (ref))
|
||||||
|
&& TREE_CODE (gimple_assign_rhs1
|
||||||
|
(SSA_NAME_DEF_STMT (ref))) == MEM_REF)
|
||||||
|
{
|
||||||
|
ref = get_base_address
|
||||||
|
(TREE_OPERAND (gimple_assign_rhs1
|
||||||
|
(SSA_NAME_DEF_STMT (ref)), 0));
|
||||||
|
ref = walk_ssa_copies (ref);
|
||||||
|
/* Find base address of the lookup and see if it looks like
|
||||||
|
vptr load. */
|
||||||
|
if (TREE_CODE (ref) == SSA_NAME
|
||||||
|
&& !SSA_NAME_IS_DEFAULT_DEF (ref)
|
||||||
|
&& gimple_assign_load_p (SSA_NAME_DEF_STMT (ref)))
|
||||||
|
{
|
||||||
|
tree ref_exp = gimple_assign_rhs1 (SSA_NAME_DEF_STMT (ref));
|
||||||
|
tree base_ref = get_ref_base_and_extent
|
||||||
|
(ref_exp, &offset2, &size, &max_size);
|
||||||
|
|
||||||
|
/* Finally verify that what we found looks like read from OTR_OBJECT
|
||||||
|
or from INSTANCE with offset OFFSET. */
|
||||||
|
if (base_ref
|
||||||
|
&& TREE_CODE (base_ref) == MEM_REF
|
||||||
|
&& ((offset2 == context->offset
|
||||||
|
&& TREE_OPERAND (base_ref, 0) == instance)
|
||||||
|
|| (!offset2 && TREE_OPERAND (base_ref, 0) == otr_object)))
|
||||||
|
{
|
||||||
|
stmt = SSA_NAME_DEF_STMT (ref);
|
||||||
|
instance_ref = ref_exp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we failed to look up the refernece in code, build our own. */
|
||||||
|
if (!instance_ref)
|
||||||
|
{
|
||||||
|
/* If the statement in question does not use memory, we can't tell
|
||||||
|
anything. */
|
||||||
|
if (!gimple_vuse (stmt))
|
||||||
|
return false;
|
||||||
|
ao_ref_init_from_ptr_and_size (&ao, otr_object, NULL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
/* Otherwise use the real reference. */
|
||||||
|
ao_ref_init (&ao, instance_ref);
|
||||||
|
|
||||||
|
/* We look for vtbl pointer read. */
|
||||||
|
ao.size = POINTER_SIZE;
|
||||||
|
ao.max_size = ao.size;
|
||||||
|
ao.ref_alias_set
|
||||||
|
= get_deref_alias_set (TREE_TYPE (BINFO_VTABLE (TYPE_BINFO (otr_type))));
|
||||||
|
|
||||||
|
if (dump_file)
|
||||||
|
{
|
||||||
|
fprintf (dump_file, "Determining dynamic type for call: ");
|
||||||
|
print_gimple_stmt (dump_file, call, 0, 0);
|
||||||
|
fprintf (dump_file, " Starting walk at: ");
|
||||||
|
print_gimple_stmt (dump_file, stmt, 0, 0);
|
||||||
|
fprintf (dump_file, " instance pointer: ");
|
||||||
|
print_generic_expr (dump_file, otr_object, TDF_SLIM);
|
||||||
|
fprintf (dump_file, " Outer instance pointer: ");
|
||||||
|
print_generic_expr (dump_file, instance, TDF_SLIM);
|
||||||
|
fprintf (dump_file, " offset: %i (bits)", (int)context->offset);
|
||||||
|
fprintf (dump_file, " vtbl reference: ");
|
||||||
|
print_generic_expr (dump_file, instance_ref, TDF_SLIM);
|
||||||
|
fprintf (dump_file, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
tci.offset = context->offset;
|
||||||
|
tci.instance = instance;
|
||||||
|
tci.vtbl_ptr_ref = instance_ref;
|
||||||
|
gcc_assert (TREE_CODE (instance) != MEM_REF);
|
||||||
|
tci.known_current_type = NULL_TREE;
|
||||||
|
tci.known_current_offset = 0;
|
||||||
|
tci.otr_type = otr_type;
|
||||||
|
tci.type_maybe_changed = false;
|
||||||
|
tci.multiple_types_encountered = false;
|
||||||
|
tci.speculative = false;
|
||||||
|
tci.seen_unanalyzed_store = false;
|
||||||
|
|
||||||
|
walk_aliased_vdefs (&ao, gimple_vuse (stmt), check_stmt_for_type_change,
|
||||||
|
&tci, NULL, &function_entry_reached);
|
||||||
|
|
||||||
|
/* If we did not find any type changing statements, we may still drop
|
||||||
|
maybe_in_construction flag if the context already have outer type.
|
||||||
|
|
||||||
|
Here we make 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.
|
||||||
|
|
||||||
|
4) placement new can not be used to change type of non-POD statically
|
||||||
|
allocated variables.
|
||||||
|
|
||||||
|
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 (this holds true only
|
||||||
|
for automatically allocated objects but at the moment we devirtualize
|
||||||
|
only these). 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 if the static outer type was found (context->outer_type)
|
||||||
|
we can safely ignore tci.speculative that is set on calls and give up
|
||||||
|
only if there was dyanmic type store that may affect given variable
|
||||||
|
(seen_unanalyzed_store) */
|
||||||
|
|
||||||
|
if (!tci.type_maybe_changed)
|
||||||
|
{
|
||||||
|
if (!context->outer_type || tci.seen_unanalyzed_store)
|
||||||
|
return false;
|
||||||
|
if (context->maybe_in_construction)
|
||||||
|
context->maybe_in_construction = false;
|
||||||
|
if (dump_file)
|
||||||
|
fprintf (dump_file, " No dynamic type change found.\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tci.known_current_type
|
||||||
|
&& !function_entry_reached
|
||||||
|
&& !tci.multiple_types_encountered)
|
||||||
|
{
|
||||||
|
if (!tci.speculative)
|
||||||
|
{
|
||||||
|
context->outer_type = tci.known_current_type;
|
||||||
|
context->offset = tci.known_current_offset;
|
||||||
|
context->maybe_in_construction = false;
|
||||||
|
context->maybe_derived_type = false;
|
||||||
|
if (dump_file)
|
||||||
|
fprintf (dump_file, " Determined dynamic type.\n");
|
||||||
|
}
|
||||||
|
else if (!context->speculative_outer_type
|
||||||
|
|| context->speculative_maybe_derived_type)
|
||||||
|
{
|
||||||
|
context->speculative_outer_type = tci.known_current_type;
|
||||||
|
context->speculative_offset = tci.known_current_offset;
|
||||||
|
context->speculative_maybe_derived_type = false;
|
||||||
|
if (dump_file)
|
||||||
|
fprintf (dump_file, " Determined speculative dynamic type.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (dump_file)
|
||||||
|
fprintf (dump_file, " Found multiple types.\n");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/* Walk bases of OUTER_TYPE that contain OTR_TYPE at OFFSET.
|
/* Walk bases of OUTER_TYPE that contain OTR_TYPE at OFFSET.
|
||||||
Lookup their respecitve virtual methods for OTR_TOKEN and OTR_TYPE
|
Lookup their respecitve virtual methods for OTR_TOKEN and OTR_TYPE
|
||||||
and insert them to NODES.
|
and insert them to NODES.
|
||||||
@ -2516,6 +3028,7 @@ devirt_variable_node_removal_hook (varpool_node *n,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Record about how many calls would benefit from given type to be final. */
|
/* Record about how many calls would benefit from given type to be final. */
|
||||||
|
|
||||||
struct odr_type_warn_count
|
struct odr_type_warn_count
|
||||||
{
|
{
|
||||||
tree type;
|
tree type;
|
||||||
@ -2524,6 +3037,7 @@ struct odr_type_warn_count
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* Record about how many calls would benefit from given method to be final. */
|
/* Record about how many calls would benefit from given method to be final. */
|
||||||
|
|
||||||
struct decl_warn_count
|
struct decl_warn_count
|
||||||
{
|
{
|
||||||
tree decl;
|
tree decl;
|
||||||
@ -2532,6 +3046,7 @@ struct decl_warn_count
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* Information about type and decl warnings. */
|
/* Information about type and decl warnings. */
|
||||||
|
|
||||||
struct final_warning_record
|
struct final_warning_record
|
||||||
{
|
{
|
||||||
gcov_type dyn_count;
|
gcov_type dyn_count;
|
||||||
|
@ -2337,11 +2337,46 @@ ipa_analyze_call_uses (struct func_body_info *fbi, gimple call)
|
|||||||
&& !virtual_method_call_p (target)))
|
&& !virtual_method_call_p (target)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
struct cgraph_edge *cs = fbi->node->get_edge (call);
|
||||||
/* If we previously turned the call into a direct call, there is
|
/* If we previously turned the call into a direct call, there is
|
||||||
no need to analyze. */
|
no need to analyze. */
|
||||||
struct cgraph_edge *cs = fbi->node->get_edge (call);
|
|
||||||
if (cs && !cs->indirect_unknown_callee)
|
if (cs && !cs->indirect_unknown_callee)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (cs->indirect_info->polymorphic)
|
||||||
|
{
|
||||||
|
tree otr_type;
|
||||||
|
HOST_WIDE_INT otr_token;
|
||||||
|
ipa_polymorphic_call_context context;
|
||||||
|
tree instance;
|
||||||
|
tree target = gimple_call_fn (call);
|
||||||
|
|
||||||
|
instance = get_polymorphic_call_info (current_function_decl,
|
||||||
|
target,
|
||||||
|
&otr_type, &otr_token,
|
||||||
|
&context, call);
|
||||||
|
|
||||||
|
if (get_dynamic_type (instance, &context,
|
||||||
|
OBJ_TYPE_REF_OBJECT (target),
|
||||||
|
otr_type, call))
|
||||||
|
{
|
||||||
|
gcc_assert (TREE_CODE (otr_type) == RECORD_TYPE);
|
||||||
|
cs->indirect_info->polymorphic = true;
|
||||||
|
cs->indirect_info->param_index = -1;
|
||||||
|
cs->indirect_info->otr_token = otr_token;
|
||||||
|
cs->indirect_info->otr_type = otr_type;
|
||||||
|
cs->indirect_info->outer_type = context.outer_type;
|
||||||
|
cs->indirect_info->speculative_outer_type = context.speculative_outer_type;
|
||||||
|
cs->indirect_info->offset = context.offset;
|
||||||
|
cs->indirect_info->speculative_offset = context.speculative_offset;
|
||||||
|
cs->indirect_info->maybe_in_construction
|
||||||
|
= context.maybe_in_construction;
|
||||||
|
cs->indirect_info->maybe_derived_type = context.maybe_derived_type;
|
||||||
|
cs->indirect_info->speculative_maybe_derived_type
|
||||||
|
= context.speculative_maybe_derived_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (TREE_CODE (target) == SSA_NAME)
|
if (TREE_CODE (target) == SSA_NAME)
|
||||||
ipa_analyze_indirect_call_uses (fbi, call, target);
|
ipa_analyze_indirect_call_uses (fbi, call, target);
|
||||||
else if (virtual_method_call_p (target))
|
else if (virtual_method_call_p (target))
|
||||||
|
@ -95,7 +95,7 @@ tree get_polymorphic_call_info (tree, tree, tree *,
|
|||||||
HOST_WIDE_INT *,
|
HOST_WIDE_INT *,
|
||||||
ipa_polymorphic_call_context *,
|
ipa_polymorphic_call_context *,
|
||||||
gimple call = NULL);
|
gimple call = NULL);
|
||||||
bool get_dynamic_type (tree, ipa_polymorphic_call_context *, tree, gimple);
|
bool get_dynamic_type (tree, ipa_polymorphic_call_context *, tree, tree, gimple);
|
||||||
bool get_polymorphic_call_info_from_invariant (ipa_polymorphic_call_context *,
|
bool get_polymorphic_call_info_from_invariant (ipa_polymorphic_call_context *,
|
||||||
tree, tree, HOST_WIDE_INT);
|
tree, tree, HOST_WIDE_INT);
|
||||||
bool decl_maybe_in_construction_p (tree, tree, gimple, tree);
|
bool decl_maybe_in_construction_p (tree, tree, gimple, tree);
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
2014-08-07 Jan Hubicka <hubicka@ucw.cz>
|
||||||
|
|
||||||
|
* g++.dg/ipa/devirt-35.C: New testcase.
|
||||||
|
* g++.dg/ipa/devirt-36.C: New testcase.
|
||||||
|
|
||||||
2014-08-07 Paolo Carlini <paolo.carlini@oracle.com>
|
2014-08-07 Paolo Carlini <paolo.carlini@oracle.com>
|
||||||
|
|
||||||
PR c++/51312
|
PR c++/51312
|
||||||
|
23
gcc/testsuite/g++.dg/ipa/devirt-35.C
Normal file
23
gcc/testsuite/g++.dg/ipa/devirt-35.C
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/* { dg-options "-O2 -fdump-ipa-devirt-details -fdump-tree-fre1-details" } */
|
||||||
|
struct A {virtual int t(void) {return 1;}};
|
||||||
|
struct B:A {B(); virtual int t(void) {return 2;}};
|
||||||
|
void test2(struct A *);
|
||||||
|
int
|
||||||
|
m(struct B *b)
|
||||||
|
{
|
||||||
|
struct A *a = new (B);
|
||||||
|
a->t(); // This call should be devirtualized by
|
||||||
|
// FRE because we know type from ctor call
|
||||||
|
((struct B *)a)->B::t(); // Make devirt possible
|
||||||
|
// C++ FE won't produce inline body without this
|
||||||
|
test2(a);
|
||||||
|
return a->t(); // This call should be devirtualized speculatively because
|
||||||
|
// test2 may change the type of A by placement new.
|
||||||
|
// C++ standard is bit imprecise about this.
|
||||||
|
}
|
||||||
|
/* { dg-final { scan-ipa-dump "converting indirect call to function virtual int B::t" "fre1" } } */
|
||||||
|
/* { dg-final { scan-ipa-dump "to virtual int B::t" "devirt" } } */
|
||||||
|
/* { dg-final { scan-ipa-dump "1 speculatively devirtualized" "devirt" } } */
|
||||||
|
/* { dg-final { cleanup-ipa-dump "devirt" } } */
|
||||||
|
/* { dg-final { cleanup-tree-dump "fre" } } */
|
||||||
|
|
25
gcc/testsuite/g++.dg/ipa/devirt-36.C
Normal file
25
gcc/testsuite/g++.dg/ipa/devirt-36.C
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/* { dg-options "-O2 -fdump-ipa-devirt-details -fdump-tree-fre1-details" } */
|
||||||
|
struct A {virtual int t(void) {return 1;}};
|
||||||
|
struct B:A {B(); virtual int t(void) {return 2;}};
|
||||||
|
struct C {int a; struct B b;};
|
||||||
|
void test2(struct A *);
|
||||||
|
int
|
||||||
|
m(struct B *b)
|
||||||
|
{
|
||||||
|
struct C *c = new (C);
|
||||||
|
struct A *a = &c->b;
|
||||||
|
a->t(); // This call should be devirtualized by
|
||||||
|
// FRE because we know type from ctor call
|
||||||
|
((struct B *)a)->B::t(); // Make devirt possible
|
||||||
|
// C++ FE won't produce inline body without this
|
||||||
|
test2(a);
|
||||||
|
return a->t(); // This call should be devirtualized speculatively because
|
||||||
|
// test2 may change the type of A by placement new.
|
||||||
|
// C++ standard is bit imprecise about this.
|
||||||
|
}
|
||||||
|
/* { dg-final { scan-ipa-dump "converting indirect call to function virtual int B::t" "fre1" } } */
|
||||||
|
/* { dg-final { scan-ipa-dump "to virtual int B::t" "devirt" } } */
|
||||||
|
/* { dg-final { scan-ipa-dump "1 speculatively devirtualized" "devirt" } } */
|
||||||
|
/* { dg-final { cleanup-ipa-dump "devirt" } } */
|
||||||
|
/* { dg-final { cleanup-tree-dump "fre" } } */
|
||||||
|
|
@ -5042,6 +5042,9 @@ ipa_tm_insert_gettmclone_call (struct cgraph_node *node,
|
|||||||
}
|
}
|
||||||
|
|
||||||
update_stmt (stmt);
|
update_stmt (stmt);
|
||||||
|
cgraph_edge *e = cgraph_node::get (current_function_decl)->get_edge (stmt);
|
||||||
|
if (e && e->indirect_info)
|
||||||
|
e->indirect_info->polymorphic = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ along with GCC; see the file COPYING3. If not see
|
|||||||
#include "domwalk.h"
|
#include "domwalk.h"
|
||||||
#include "ipa-prop.h"
|
#include "ipa-prop.h"
|
||||||
#include "tree-ssa-propagate.h"
|
#include "tree-ssa-propagate.h"
|
||||||
|
#include "ipa-utils.h"
|
||||||
|
|
||||||
/* TODO:
|
/* TODO:
|
||||||
|
|
||||||
@ -4360,12 +4361,41 @@ eliminate_dom_walker::before_dom_children (basic_block b)
|
|||||||
{
|
{
|
||||||
tree fn = gimple_call_fn (stmt);
|
tree fn = gimple_call_fn (stmt);
|
||||||
if (fn
|
if (fn
|
||||||
&& TREE_CODE (fn) == OBJ_TYPE_REF
|
&& flag_devirtualize
|
||||||
&& TREE_CODE (OBJ_TYPE_REF_EXPR (fn)) == SSA_NAME)
|
&& virtual_method_call_p (fn))
|
||||||
{
|
{
|
||||||
fn = ipa_intraprocedural_devirtualization (stmt);
|
tree otr_type;
|
||||||
if (fn && dbg_cnt (devirt))
|
HOST_WIDE_INT otr_token;
|
||||||
|
ipa_polymorphic_call_context context;
|
||||||
|
tree instance;
|
||||||
|
bool final;
|
||||||
|
|
||||||
|
instance = get_polymorphic_call_info (current_function_decl,
|
||||||
|
fn,
|
||||||
|
&otr_type, &otr_token, &context, stmt);
|
||||||
|
|
||||||
|
get_dynamic_type (instance, &context,
|
||||||
|
OBJ_TYPE_REF_OBJECT (fn), otr_type, stmt);
|
||||||
|
|
||||||
|
vec <cgraph_node *>targets
|
||||||
|
= possible_polymorphic_call_targets (obj_type_ref_class (fn),
|
||||||
|
tree_to_uhwi
|
||||||
|
(OBJ_TYPE_REF_TOKEN (fn)),
|
||||||
|
context,
|
||||||
|
&final);
|
||||||
|
if (dump_enabled_p ())
|
||||||
|
dump_possible_polymorphic_call_targets (dump_file,
|
||||||
|
obj_type_ref_class (fn),
|
||||||
|
tree_to_uhwi
|
||||||
|
(OBJ_TYPE_REF_TOKEN (fn)),
|
||||||
|
context);
|
||||||
|
if (final && targets.length () <= 1 && dbg_cnt (devirt))
|
||||||
{
|
{
|
||||||
|
tree fn;
|
||||||
|
if (targets.length () == 1)
|
||||||
|
fn = targets[0]->decl;
|
||||||
|
else
|
||||||
|
fn = builtin_decl_implicit (BUILT_IN_UNREACHABLE);
|
||||||
if (dump_enabled_p ())
|
if (dump_enabled_p ())
|
||||||
{
|
{
|
||||||
location_t loc = gimple_location_safe (stmt);
|
location_t loc = gimple_location_safe (stmt);
|
||||||
@ -4377,6 +4407,8 @@ eliminate_dom_walker::before_dom_children (basic_block b)
|
|||||||
gimple_call_set_fndecl (stmt, fn);
|
gimple_call_set_fndecl (stmt, fn);
|
||||||
gimple_set_modified (stmt, true);
|
gimple_set_modified (stmt, true);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
gcc_assert (!ipa_intraprocedural_devirtualization (stmt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user