mirror of
https://gcc.gnu.org/git/gcc.git
synced 2024-11-23 19:03:59 +08:00
4f4820964e
I managed to commit a hack setting offset to 0 in ipa_polymorphic_call_context::set_by_invariant. This makes it to give up on multiple inheritance, but most likely won't give bad code since the ohter base will be of different type. gcc/ChangeLog: * ipa-polymorphic-call.cc (ipa_polymorphic_call_context::set_by_invariant): Remove accidental hack reseting offset.
2615 lines
80 KiB
C++
2615 lines
80 KiB
C++
/* Analysis of polymorphic call context.
|
|
Copyright (C) 2013-2024 Free Software Foundation, Inc.
|
|
Contributed by Jan Hubicka
|
|
|
|
This file is part of GCC.
|
|
|
|
GCC is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU General Public License as published by the Free
|
|
Software Foundation; either version 3, or (at your option) any later
|
|
version.
|
|
|
|
GCC is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GCC; see the file COPYING3. If not see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "backend.h"
|
|
#include "rtl.h"
|
|
#include "tree.h"
|
|
#include "gimple.h"
|
|
#include "tree-pass.h"
|
|
#include "tree-ssa-operands.h"
|
|
#include "streamer-hooks.h"
|
|
#include "cgraph.h"
|
|
#include "data-streamer.h"
|
|
#include "diagnostic.h"
|
|
#include "alias.h"
|
|
#include "fold-const.h"
|
|
#include "calls.h"
|
|
#include "ipa-utils.h"
|
|
#include "tree-dfa.h"
|
|
#include "gimple-pretty-print.h"
|
|
#include "tree-into-ssa.h"
|
|
#include "alloc-pool.h"
|
|
#include "symbol-summary.h"
|
|
#include "symtab-thunks.h"
|
|
|
|
/* Return true when TYPE contains an polymorphic type and thus is interesting
|
|
for devirtualization machinery. */
|
|
|
|
static bool contains_type_p (tree, HOST_WIDE_INT, tree,
|
|
bool consider_placement_new = true,
|
|
bool consider_bases = true);
|
|
|
|
bool
|
|
contains_polymorphic_type_p (const_tree type)
|
|
{
|
|
type = TYPE_MAIN_VARIANT (type);
|
|
|
|
if (RECORD_OR_UNION_TYPE_P (type))
|
|
{
|
|
if (TYPE_BINFO (type)
|
|
&& polymorphic_type_binfo_p (TYPE_BINFO (type)))
|
|
return true;
|
|
for (tree fld = TYPE_FIELDS (type); fld; fld = DECL_CHAIN (fld))
|
|
if (TREE_CODE (fld) == FIELD_DECL
|
|
&& !DECL_ARTIFICIAL (fld)
|
|
&& contains_polymorphic_type_p (TREE_TYPE (fld)))
|
|
return true;
|
|
return false;
|
|
}
|
|
if (TREE_CODE (type) == ARRAY_TYPE)
|
|
return contains_polymorphic_type_p (TREE_TYPE (type));
|
|
return false;
|
|
}
|
|
|
|
/* Return true if it seems valid to use placement new to build EXPECTED_TYPE
|
|
at position CUR_OFFSET within TYPE.
|
|
|
|
POD can be changed to an instance of a polymorphic type by
|
|
placement new. Here we play safe and assume that any
|
|
non-polymorphic type is POD. */
|
|
bool
|
|
possible_placement_new (tree type, tree expected_type,
|
|
HOST_WIDE_INT cur_offset)
|
|
{
|
|
if (cur_offset < 0)
|
|
return true;
|
|
return ((TREE_CODE (type) != RECORD_TYPE
|
|
|| !TYPE_BINFO (type)
|
|
|| cur_offset >= POINTER_SIZE
|
|
|| !polymorphic_type_binfo_p (TYPE_BINFO (type)))
|
|
&& (!TYPE_SIZE (type)
|
|
|| !tree_fits_shwi_p (TYPE_SIZE (type))
|
|
|| (cur_offset
|
|
+ (expected_type ? tree_to_uhwi (TYPE_SIZE (expected_type))
|
|
: POINTER_SIZE)
|
|
<= tree_to_uhwi (TYPE_SIZE (type)))));
|
|
}
|
|
|
|
/* THIS->OUTER_TYPE is a type of memory object where object of OTR_TYPE
|
|
is contained at THIS->OFFSET. Walk the memory representation of
|
|
THIS->OUTER_TYPE and find the outermost class type that match
|
|
OTR_TYPE or contain OTR_TYPE as a base. Update THIS
|
|
to represent it.
|
|
|
|
If OTR_TYPE is NULL, just find outermost polymorphic type with
|
|
virtual table present at position OFFSET.
|
|
|
|
For example when THIS represents type
|
|
class A
|
|
{
|
|
int a;
|
|
class B b;
|
|
}
|
|
and we look for type at offset sizeof(int), we end up with B and offset 0.
|
|
If the same is produced by multiple inheritance, we end up with A and offset
|
|
sizeof(int).
|
|
|
|
If we cannot find corresponding class, give up by setting
|
|
THIS->OUTER_TYPE to OTR_TYPE and THIS->OFFSET to NULL.
|
|
Return true when lookup was successful.
|
|
|
|
When CONSIDER_PLACEMENT_NEW is false, reject contexts that may be made
|
|
valid only via allocation of new polymorphic type inside by means
|
|
of placement new.
|
|
|
|
When CONSIDER_BASES is false, only look for actual fields, not base types
|
|
of TYPE. */
|
|
|
|
bool
|
|
ipa_polymorphic_call_context::restrict_to_inner_class (tree otr_type,
|
|
bool consider_placement_new,
|
|
bool consider_bases)
|
|
{
|
|
tree type = outer_type;
|
|
HOST_WIDE_INT cur_offset = offset;
|
|
bool speculative = false;
|
|
bool size_unknown = false;
|
|
unsigned HOST_WIDE_INT otr_type_size = POINTER_SIZE;
|
|
|
|
/* Update OUTER_TYPE to match EXPECTED_TYPE if it is not set. */
|
|
if (!outer_type)
|
|
{
|
|
clear_outer_type (otr_type);
|
|
type = otr_type;
|
|
cur_offset = 0;
|
|
}
|
|
/* See if OFFSET points inside OUTER_TYPE. If it does not, we know
|
|
that the context is either invalid, or the instance type must be
|
|
derived from OUTER_TYPE.
|
|
|
|
Because the instance type may contain field whose type is of OUTER_TYPE,
|
|
we cannot derive any effective information about it.
|
|
|
|
TODO: In the case we know all derived types, we can definitely do better
|
|
here. */
|
|
else if (TYPE_SIZE (outer_type)
|
|
&& tree_fits_shwi_p (TYPE_SIZE (outer_type))
|
|
&& tree_to_shwi (TYPE_SIZE (outer_type)) >= 0
|
|
&& tree_to_shwi (TYPE_SIZE (outer_type)) <= offset)
|
|
{
|
|
bool der = maybe_derived_type; /* clear_outer_type will reset it. */
|
|
bool dyn = dynamic;
|
|
clear_outer_type (otr_type);
|
|
type = otr_type;
|
|
cur_offset = 0;
|
|
|
|
/* If derived type is not allowed, we know that the context is invalid.
|
|
For dynamic types, we really do not have information about
|
|
size of the memory location. It is possible that completely
|
|
different type is stored after outer_type. */
|
|
if (!der && !dyn)
|
|
{
|
|
clear_speculation ();
|
|
invalid = true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (otr_type && TYPE_SIZE (otr_type)
|
|
&& tree_fits_shwi_p (TYPE_SIZE (otr_type)))
|
|
otr_type_size = tree_to_uhwi (TYPE_SIZE (otr_type));
|
|
|
|
if (!type || offset < 0)
|
|
goto no_useful_type_info;
|
|
|
|
/* Find the sub-object the constant actually refers to and mark whether it is
|
|
an artificial one (as opposed to a user-defined one).
|
|
|
|
This loop is performed twice; first time for outer_type and second time
|
|
for speculative_outer_type. The second run has SPECULATIVE set. */
|
|
while (true)
|
|
{
|
|
unsigned HOST_WIDE_INT pos, size;
|
|
tree fld;
|
|
|
|
/* If we do not know size of TYPE, we need to be more conservative
|
|
about accepting cases where we cannot find EXPECTED_TYPE.
|
|
Generally the types that do matter here are of constant size.
|
|
Size_unknown case should be very rare. */
|
|
if (TYPE_SIZE (type)
|
|
&& tree_fits_shwi_p (TYPE_SIZE (type))
|
|
&& tree_to_shwi (TYPE_SIZE (type)) >= 0)
|
|
size_unknown = false;
|
|
else
|
|
size_unknown = true;
|
|
|
|
/* On a match, just return what we found. */
|
|
if ((otr_type
|
|
&& types_odr_comparable (type, otr_type)
|
|
&& types_same_for_odr (type, otr_type))
|
|
|| (!otr_type
|
|
&& TREE_CODE (type) == RECORD_TYPE
|
|
&& TYPE_BINFO (type)
|
|
&& polymorphic_type_binfo_p (TYPE_BINFO (type))))
|
|
{
|
|
if (speculative)
|
|
{
|
|
/* If we did not match the offset, just give up on speculation. */
|
|
if (cur_offset != 0
|
|
/* Also check if speculation did not end up being same as
|
|
non-speculation. */
|
|
|| (types_must_be_same_for_odr (speculative_outer_type,
|
|
outer_type)
|
|
&& (maybe_derived_type
|
|
== speculative_maybe_derived_type)))
|
|
clear_speculation ();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
/* If type is known to be final, do not worry about derived
|
|
types. Testing it here may help us to avoid speculation. */
|
|
if (otr_type && TREE_CODE (outer_type) == RECORD_TYPE
|
|
&& (!in_lto_p || odr_type_p (outer_type))
|
|
&& type_with_linkage_p (outer_type)
|
|
&& type_known_to_have_no_derivations_p (outer_type))
|
|
maybe_derived_type = false;
|
|
|
|
/* Type cannot contain itself on an non-zero offset. In that case
|
|
just give up. Still accept the case where size is now known.
|
|
Either the second copy may appear past the end of type or within
|
|
the non-POD buffer located inside the variably sized type
|
|
itself. */
|
|
if (cur_offset != 0)
|
|
goto no_useful_type_info;
|
|
/* If we determined type precisely or we have no clue on
|
|
speculation, we are done. */
|
|
if (!maybe_derived_type || !speculative_outer_type
|
|
|| !speculation_consistent_p (speculative_outer_type,
|
|
speculative_offset,
|
|
speculative_maybe_derived_type,
|
|
otr_type))
|
|
{
|
|
clear_speculation ();
|
|
return true;
|
|
}
|
|
/* Otherwise look into speculation now. */
|
|
else
|
|
{
|
|
speculative = true;
|
|
type = speculative_outer_type;
|
|
cur_offset = speculative_offset;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Walk fields and find corresponding on at OFFSET. */
|
|
if (TREE_CODE (type) == RECORD_TYPE)
|
|
{
|
|
for (fld = TYPE_FIELDS (type); fld; fld = DECL_CHAIN (fld))
|
|
{
|
|
if (TREE_CODE (fld) != FIELD_DECL
|
|
|| TREE_TYPE (fld) == error_mark_node)
|
|
continue;
|
|
|
|
pos = int_bit_position (fld);
|
|
if (pos > (unsigned HOST_WIDE_INT)cur_offset)
|
|
continue;
|
|
|
|
/* Do not consider vptr itself. Not even for placement new. */
|
|
if (!pos && DECL_ARTIFICIAL (fld)
|
|
&& POINTER_TYPE_P (TREE_TYPE (fld))
|
|
&& TYPE_BINFO (type)
|
|
&& polymorphic_type_binfo_p (TYPE_BINFO (type)))
|
|
continue;
|
|
|
|
if (!DECL_SIZE (fld) || !tree_fits_uhwi_p (DECL_SIZE (fld)))
|
|
goto no_useful_type_info;
|
|
size = tree_to_uhwi (DECL_SIZE (fld));
|
|
|
|
/* We can always skip types smaller than pointer size:
|
|
those cannot contain a virtual table pointer.
|
|
|
|
Disqualifying fields that are too small to fit OTR_TYPE
|
|
saves work needed to walk them for no benefit.
|
|
Because of the way the bases are packed into a class, the
|
|
field's size may be smaller than type size, so it needs
|
|
to be done with a care. */
|
|
|
|
if (pos <= (unsigned HOST_WIDE_INT)cur_offset
|
|
&& (pos + size) >= (unsigned HOST_WIDE_INT)cur_offset
|
|
+ POINTER_SIZE
|
|
&& (!otr_type
|
|
|| !TYPE_SIZE (TREE_TYPE (fld))
|
|
|| !tree_fits_shwi_p (TYPE_SIZE (TREE_TYPE (fld)))
|
|
|| (pos + tree_to_uhwi (TYPE_SIZE (TREE_TYPE (fld))))
|
|
>= cur_offset + otr_type_size))
|
|
break;
|
|
}
|
|
|
|
if (!fld)
|
|
goto no_useful_type_info;
|
|
|
|
type = TYPE_MAIN_VARIANT (TREE_TYPE (fld));
|
|
cur_offset -= pos;
|
|
/* DECL_ARTIFICIAL represents a basetype. */
|
|
if (!DECL_ARTIFICIAL (fld))
|
|
{
|
|
if (!speculative)
|
|
{
|
|
outer_type = type;
|
|
offset = cur_offset;
|
|
/* As soon as we see an field containing the type,
|
|
we know we are not looking for derivations. */
|
|
maybe_derived_type = false;
|
|
}
|
|
else
|
|
{
|
|
speculative_outer_type = type;
|
|
speculative_offset = cur_offset;
|
|
speculative_maybe_derived_type = false;
|
|
}
|
|
}
|
|
else if (!consider_bases)
|
|
goto no_useful_type_info;
|
|
}
|
|
else if (TREE_CODE (type) == ARRAY_TYPE)
|
|
{
|
|
tree subtype = TYPE_MAIN_VARIANT (TREE_TYPE (type));
|
|
|
|
/* Give up if we don't know array field size.
|
|
Also give up on non-polymorphic types as they are used
|
|
as buffers for placement new. */
|
|
if (!TYPE_SIZE (subtype)
|
|
|| !tree_fits_shwi_p (TYPE_SIZE (subtype))
|
|
|| tree_to_shwi (TYPE_SIZE (subtype)) <= 0
|
|
|| !contains_polymorphic_type_p (subtype))
|
|
goto no_useful_type_info;
|
|
|
|
HOST_WIDE_INT new_offset = cur_offset % tree_to_shwi (TYPE_SIZE (subtype));
|
|
|
|
/* We may see buffer for placement new. In this case the expected type
|
|
can be bigger than the subtype. */
|
|
if (TYPE_SIZE (subtype)
|
|
&& (cur_offset + otr_type_size
|
|
> tree_to_uhwi (TYPE_SIZE (subtype))))
|
|
goto no_useful_type_info;
|
|
|
|
cur_offset = new_offset;
|
|
type = TYPE_MAIN_VARIANT (subtype);
|
|
if (!speculative)
|
|
{
|
|
outer_type = type;
|
|
offset = cur_offset;
|
|
maybe_derived_type = false;
|
|
}
|
|
else
|
|
{
|
|
speculative_outer_type = type;
|
|
speculative_offset = cur_offset;
|
|
speculative_maybe_derived_type = false;
|
|
}
|
|
}
|
|
/* Give up on anything else. */
|
|
else
|
|
{
|
|
no_useful_type_info:
|
|
if (maybe_derived_type && !speculative
|
|
&& TREE_CODE (outer_type) == RECORD_TYPE
|
|
&& TREE_CODE (otr_type) == RECORD_TYPE
|
|
&& TYPE_BINFO (otr_type)
|
|
&& !offset
|
|
&& get_binfo_at_offset (TYPE_BINFO (otr_type), 0, outer_type))
|
|
{
|
|
clear_outer_type (otr_type);
|
|
if (!speculative_outer_type
|
|
|| !speculation_consistent_p (speculative_outer_type,
|
|
speculative_offset,
|
|
speculative_maybe_derived_type,
|
|
otr_type))
|
|
clear_speculation ();
|
|
if (speculative_outer_type)
|
|
{
|
|
speculative = true;
|
|
type = speculative_outer_type;
|
|
cur_offset = speculative_offset;
|
|
}
|
|
else
|
|
return true;
|
|
}
|
|
/* We found no way to embed EXPECTED_TYPE in TYPE.
|
|
We still permit two special cases - placement new and
|
|
the case of variadic types containing themselves. */
|
|
if (!speculative
|
|
&& consider_placement_new
|
|
&& (size_unknown || !type || maybe_derived_type
|
|
|| possible_placement_new (type, otr_type, cur_offset)))
|
|
{
|
|
/* In these weird cases we want to accept the context.
|
|
In non-speculative run we have no useful outer_type info
|
|
(TODO: we may eventually want to record upper bound on the
|
|
type size that can be used to prune the walk),
|
|
but we still want to consider speculation that may
|
|
give useful info. */
|
|
if (!speculative)
|
|
{
|
|
clear_outer_type (otr_type);
|
|
if (!speculative_outer_type
|
|
|| !speculation_consistent_p (speculative_outer_type,
|
|
speculative_offset,
|
|
speculative_maybe_derived_type,
|
|
otr_type))
|
|
clear_speculation ();
|
|
if (speculative_outer_type)
|
|
{
|
|
speculative = true;
|
|
type = speculative_outer_type;
|
|
cur_offset = speculative_offset;
|
|
}
|
|
else
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
clear_speculation ();
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
clear_speculation ();
|
|
if (speculative)
|
|
return true;
|
|
clear_outer_type (otr_type);
|
|
invalid = true;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Return true if OUTER_TYPE contains OTR_TYPE at OFFSET.
|
|
CONSIDER_PLACEMENT_NEW makes function to accept cases where OTR_TYPE can
|
|
be built within OUTER_TYPE by means of placement new. CONSIDER_BASES makes
|
|
function to accept cases where OTR_TYPE appears as base of OUTER_TYPE or as
|
|
base of one of fields of OUTER_TYPE. */
|
|
|
|
static bool
|
|
contains_type_p (tree outer_type, HOST_WIDE_INT offset,
|
|
tree otr_type,
|
|
bool consider_placement_new,
|
|
bool consider_bases)
|
|
{
|
|
ipa_polymorphic_call_context context;
|
|
|
|
/* Check that type is within range. */
|
|
if (offset < 0)
|
|
return false;
|
|
|
|
/* PR ipa/71207
|
|
As OUTER_TYPE can be a type which has a diamond virtual inheritance,
|
|
it's not necessary that INNER_TYPE will fit within OUTER_TYPE with
|
|
a given offset. It can happen that INNER_TYPE also contains a base object,
|
|
however it would point to the same instance in the OUTER_TYPE. */
|
|
|
|
context.offset = offset;
|
|
context.outer_type = TYPE_MAIN_VARIANT (outer_type);
|
|
context.maybe_derived_type = false;
|
|
context.dynamic = false;
|
|
return context.restrict_to_inner_class (otr_type, consider_placement_new,
|
|
consider_bases);
|
|
}
|
|
|
|
|
|
/* Return a FUNCTION_DECL if FN represent a constructor or destructor.
|
|
If CHECK_CLONES is true, also check for clones of ctor/dtors. */
|
|
|
|
tree
|
|
polymorphic_ctor_dtor_p (tree fn, bool check_clones)
|
|
{
|
|
if (TREE_CODE (TREE_TYPE (fn)) != METHOD_TYPE
|
|
|| (!DECL_CXX_CONSTRUCTOR_P (fn) && !DECL_CXX_DESTRUCTOR_P (fn)))
|
|
{
|
|
if (!check_clones)
|
|
return NULL_TREE;
|
|
|
|
/* Watch for clones where we constant propagated the first
|
|
argument (pointer to the instance). */
|
|
fn = DECL_ABSTRACT_ORIGIN (fn);
|
|
if (!fn
|
|
|| TREE_CODE (TREE_TYPE (fn)) != METHOD_TYPE
|
|
|| (!DECL_CXX_CONSTRUCTOR_P (fn) && !DECL_CXX_DESTRUCTOR_P (fn)))
|
|
return NULL_TREE;
|
|
}
|
|
|
|
if (flags_from_decl_or_type (fn) & (ECF_PURE | ECF_CONST))
|
|
return NULL_TREE;
|
|
|
|
return fn;
|
|
}
|
|
|
|
/* Return a FUNCTION_DECL if BLOCK represents a constructor or destructor.
|
|
If CHECK_CLONES is true, also check for clones of ctor/dtors. */
|
|
|
|
tree
|
|
inlined_polymorphic_ctor_dtor_block_p (tree block, bool check_clones)
|
|
{
|
|
tree fn = block_ultimate_origin (block);
|
|
if (fn == NULL || TREE_CODE (fn) != FUNCTION_DECL)
|
|
return NULL_TREE;
|
|
|
|
return polymorphic_ctor_dtor_p (fn, check_clones);
|
|
}
|
|
|
|
|
|
/* We know that the instance is stored in variable or parameter
|
|
(not dynamically allocated) and we want to disprove the fact
|
|
that it may be in construction at invocation of CALL.
|
|
|
|
BASE represents memory location where instance is stored.
|
|
If BASE is NULL, it is assumed to be global memory.
|
|
OUTER_TYPE is known type of the instance or NULL if not
|
|
known.
|
|
|
|
For the variable to be in construction we actually need to
|
|
be in constructor of corresponding global variable or
|
|
the inline stack of CALL must contain the constructor.
|
|
Check this condition. This check works safely only before
|
|
IPA passes, because inline stacks may become out of date
|
|
later. */
|
|
|
|
bool
|
|
decl_maybe_in_construction_p (tree base, tree outer_type,
|
|
gimple *call, tree function)
|
|
{
|
|
if (outer_type)
|
|
outer_type = TYPE_MAIN_VARIANT (outer_type);
|
|
gcc_assert (!base || DECL_P (base));
|
|
|
|
/* After inlining the code unification optimizations may invalidate
|
|
inline stacks. Also we need to give up on global variables after
|
|
IPA, because addresses of these may have been propagated to their
|
|
constructors. */
|
|
if (DECL_STRUCT_FUNCTION (function)->after_inlining)
|
|
return true;
|
|
|
|
/* Pure functions cannot do any changes on the dynamic type;
|
|
that require writing to memory. */
|
|
if ((!base || !auto_var_in_fn_p (base, function))
|
|
&& flags_from_decl_or_type (function) & (ECF_PURE | ECF_CONST))
|
|
return false;
|
|
|
|
bool check_clones = !base || is_global_var (base);
|
|
for (tree block = gimple_block (call); block && TREE_CODE (block) == BLOCK;
|
|
block = BLOCK_SUPERCONTEXT (block))
|
|
if (tree fn = inlined_polymorphic_ctor_dtor_block_p (block, check_clones))
|
|
{
|
|
tree type = TYPE_METHOD_BASETYPE (TREE_TYPE (fn));
|
|
|
|
if (!outer_type || !types_odr_comparable (type, outer_type))
|
|
{
|
|
if (TREE_CODE (type) == RECORD_TYPE
|
|
&& TYPE_BINFO (type)
|
|
&& polymorphic_type_binfo_p (TYPE_BINFO (type)))
|
|
return true;
|
|
}
|
|
else if (types_same_for_odr (type, outer_type))
|
|
return true;
|
|
}
|
|
|
|
if (!base || (VAR_P (base) && is_global_var (base)))
|
|
{
|
|
if (TREE_CODE (TREE_TYPE (function)) != METHOD_TYPE
|
|
|| (!DECL_CXX_CONSTRUCTOR_P (function)
|
|
&& !DECL_CXX_DESTRUCTOR_P (function)))
|
|
{
|
|
if (!DECL_ABSTRACT_ORIGIN (function))
|
|
return false;
|
|
/* Watch for clones where we constant propagated the first
|
|
argument (pointer to the instance). */
|
|
function = DECL_ABSTRACT_ORIGIN (function);
|
|
if (!function
|
|
|| TREE_CODE (TREE_TYPE (function)) != METHOD_TYPE
|
|
|| (!DECL_CXX_CONSTRUCTOR_P (function)
|
|
&& !DECL_CXX_DESTRUCTOR_P (function)))
|
|
return false;
|
|
}
|
|
tree type = TYPE_METHOD_BASETYPE (TREE_TYPE (function));
|
|
if (!outer_type || !types_odr_comparable (type, outer_type))
|
|
{
|
|
if (TREE_CODE (type) == RECORD_TYPE
|
|
&& TYPE_BINFO (type)
|
|
&& polymorphic_type_binfo_p (TYPE_BINFO (type)))
|
|
return true;
|
|
}
|
|
else if (types_same_for_odr (type, outer_type))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Dump human readable context to F. If NEWLINE is true, it will be terminated
|
|
by a newline. */
|
|
|
|
void
|
|
ipa_polymorphic_call_context::dump (FILE *f, bool newline) const
|
|
{
|
|
fprintf (f, " ");
|
|
if (invalid)
|
|
fprintf (f, "Call is known to be undefined");
|
|
else
|
|
{
|
|
if (useless_p ())
|
|
fprintf (f, "nothing known");
|
|
if (outer_type || offset)
|
|
{
|
|
fprintf (f, "Outer type%s:", dynamic ? " (dynamic)":"");
|
|
print_generic_expr (f, outer_type, TDF_SLIM);
|
|
if (maybe_derived_type)
|
|
fprintf (f, " (or a derived type)");
|
|
if (maybe_in_construction)
|
|
fprintf (f, " (maybe in construction)");
|
|
fprintf (f, " offset " HOST_WIDE_INT_PRINT_DEC,
|
|
offset);
|
|
}
|
|
if (speculative_outer_type)
|
|
{
|
|
if (outer_type || offset)
|
|
fprintf (f, " ");
|
|
fprintf (f, "Speculative outer type:");
|
|
print_generic_expr (f, speculative_outer_type, TDF_SLIM);
|
|
if (speculative_maybe_derived_type)
|
|
fprintf (f, " (or a derived type)");
|
|
fprintf (f, " at offset " HOST_WIDE_INT_PRINT_DEC,
|
|
speculative_offset);
|
|
}
|
|
}
|
|
if (newline)
|
|
fprintf(f, "\n");
|
|
}
|
|
|
|
/* Print context to stderr. */
|
|
|
|
void
|
|
ipa_polymorphic_call_context::debug () const
|
|
{
|
|
dump (stderr);
|
|
}
|
|
|
|
/* Stream out the context to OB. */
|
|
|
|
void
|
|
ipa_polymorphic_call_context::stream_out (struct output_block *ob) const
|
|
{
|
|
struct bitpack_d bp = bitpack_create (ob->main_stream);
|
|
|
|
bp_pack_value (&bp, invalid, 1);
|
|
bp_pack_value (&bp, maybe_in_construction, 1);
|
|
bp_pack_value (&bp, maybe_derived_type, 1);
|
|
bp_pack_value (&bp, speculative_maybe_derived_type, 1);
|
|
bp_pack_value (&bp, dynamic, 1);
|
|
bp_pack_value (&bp, outer_type != NULL, 1);
|
|
bp_pack_value (&bp, offset != 0, 1);
|
|
bp_pack_value (&bp, speculative_outer_type != NULL, 1);
|
|
streamer_write_bitpack (&bp);
|
|
|
|
if (outer_type != NULL)
|
|
stream_write_tree (ob, outer_type, true);
|
|
if (offset)
|
|
streamer_write_hwi (ob, offset);
|
|
if (speculative_outer_type != NULL)
|
|
{
|
|
stream_write_tree (ob, speculative_outer_type, true);
|
|
streamer_write_hwi (ob, speculative_offset);
|
|
}
|
|
else
|
|
gcc_assert (!speculative_offset);
|
|
}
|
|
|
|
/* Stream in the context from IB and DATA_IN. */
|
|
|
|
void
|
|
ipa_polymorphic_call_context::stream_in (class lto_input_block *ib,
|
|
class data_in *data_in)
|
|
{
|
|
struct bitpack_d bp = streamer_read_bitpack (ib);
|
|
|
|
invalid = bp_unpack_value (&bp, 1);
|
|
maybe_in_construction = bp_unpack_value (&bp, 1);
|
|
maybe_derived_type = bp_unpack_value (&bp, 1);
|
|
speculative_maybe_derived_type = bp_unpack_value (&bp, 1);
|
|
dynamic = bp_unpack_value (&bp, 1);
|
|
bool outer_type_p = bp_unpack_value (&bp, 1);
|
|
bool offset_p = bp_unpack_value (&bp, 1);
|
|
bool speculative_outer_type_p = bp_unpack_value (&bp, 1);
|
|
|
|
if (outer_type_p)
|
|
outer_type = stream_read_tree (ib, data_in);
|
|
else
|
|
outer_type = NULL;
|
|
if (offset_p)
|
|
offset = (HOST_WIDE_INT) streamer_read_hwi (ib);
|
|
else
|
|
offset = 0;
|
|
if (speculative_outer_type_p)
|
|
{
|
|
speculative_outer_type = stream_read_tree (ib, data_in);
|
|
speculative_offset = (HOST_WIDE_INT) streamer_read_hwi (ib);
|
|
}
|
|
else
|
|
{
|
|
speculative_outer_type = NULL;
|
|
speculative_offset = 0;
|
|
}
|
|
}
|
|
|
|
/* Produce polymorphic call context for call method of instance
|
|
that is located within BASE (that is assumed to be a decl) at offset OFF. */
|
|
|
|
void
|
|
ipa_polymorphic_call_context::set_by_decl (tree base, HOST_WIDE_INT off)
|
|
{
|
|
gcc_assert (DECL_P (base));
|
|
clear_speculation ();
|
|
|
|
if (!contains_polymorphic_type_p (TREE_TYPE (base)))
|
|
{
|
|
clear_outer_type ();
|
|
offset = off;
|
|
return;
|
|
}
|
|
outer_type = TYPE_MAIN_VARIANT (TREE_TYPE (base));
|
|
offset = off;
|
|
/* Make very conservative assumption that all objects
|
|
may be in construction.
|
|
|
|
It is up to caller to revisit this via
|
|
get_dynamic_type or decl_maybe_in_construction_p. */
|
|
maybe_in_construction = true;
|
|
maybe_derived_type = false;
|
|
dynamic = false;
|
|
}
|
|
|
|
/* CST is an invariant (address of decl), try to get meaningful
|
|
polymorphic call context for polymorphic call of method
|
|
if instance of OTR_TYPE that is located at offset OFF of this invariant.
|
|
Return FALSE if nothing meaningful can be found. */
|
|
|
|
bool
|
|
ipa_polymorphic_call_context::set_by_invariant (tree cst,
|
|
tree otr_type,
|
|
HOST_WIDE_INT off)
|
|
{
|
|
poly_int64 offset2, size, max_size;
|
|
bool reverse;
|
|
tree base;
|
|
|
|
invalid = false;
|
|
clear_outer_type (otr_type);
|
|
|
|
if (TREE_CODE (cst) != ADDR_EXPR)
|
|
return false;
|
|
|
|
cst = TREE_OPERAND (cst, 0);
|
|
base = get_ref_base_and_extent (cst, &offset2, &size, &max_size, &reverse);
|
|
if (!DECL_P (base) || !known_size_p (max_size) || maybe_ne (max_size, size))
|
|
return false;
|
|
|
|
/* Only type inconsistent programs can have otr_type that is
|
|
not part of outer type. */
|
|
if (otr_type && !contains_type_p (TREE_TYPE (base), off, otr_type))
|
|
return false;
|
|
|
|
set_by_decl (base, off);
|
|
return true;
|
|
}
|
|
|
|
/* See if OP is SSA name initialized as a copy or by single assignment.
|
|
If so, walk the SSA graph up. Because simple PHI conditional is considered
|
|
copy, GLOBAL_VISITED may be used to avoid infinite loop walking the SSA
|
|
graph. */
|
|
|
|
static tree
|
|
walk_ssa_copies (tree op, hash_set<tree> **global_visited = NULL)
|
|
{
|
|
hash_set <tree> *visited = NULL;
|
|
STRIP_NOPS (op);
|
|
while (TREE_CODE (op) == SSA_NAME
|
|
&& !SSA_NAME_IS_DEFAULT_DEF (op)
|
|
/* We might be called via fold_stmt during cfgcleanup where
|
|
SSA form need not be up-to-date. */
|
|
&& !name_registered_for_update_p (op)
|
|
&& (gimple_assign_single_p (SSA_NAME_DEF_STMT (op))
|
|
|| gimple_code (SSA_NAME_DEF_STMT (op)) == GIMPLE_PHI))
|
|
{
|
|
if (global_visited)
|
|
{
|
|
if (!*global_visited)
|
|
*global_visited = new hash_set<tree>;
|
|
if ((*global_visited)->add (op))
|
|
goto done;
|
|
}
|
|
else
|
|
{
|
|
if (!visited)
|
|
visited = new hash_set<tree>;
|
|
if (visited->add (op))
|
|
goto done;
|
|
}
|
|
/* Special case
|
|
if (ptr == 0)
|
|
ptr = 0;
|
|
else
|
|
ptr = ptr.foo;
|
|
This pattern is implicitly produced for casts to non-primary
|
|
bases. When doing context analysis, we do not really care
|
|
about the case pointer is NULL, because the call will be
|
|
undefined anyway. */
|
|
if (gimple_code (SSA_NAME_DEF_STMT (op)) == GIMPLE_PHI)
|
|
{
|
|
gimple *phi = SSA_NAME_DEF_STMT (op);
|
|
|
|
if (gimple_phi_num_args (phi) > 2)
|
|
goto done;
|
|
if (gimple_phi_num_args (phi) == 1)
|
|
op = gimple_phi_arg_def (phi, 0);
|
|
else if (integer_zerop (gimple_phi_arg_def (phi, 0)))
|
|
op = gimple_phi_arg_def (phi, 1);
|
|
else if (integer_zerop (gimple_phi_arg_def (phi, 1)))
|
|
op = gimple_phi_arg_def (phi, 0);
|
|
else
|
|
goto done;
|
|
}
|
|
else
|
|
{
|
|
if (gimple_assign_load_p (SSA_NAME_DEF_STMT (op)))
|
|
goto done;
|
|
op = gimple_assign_rhs1 (SSA_NAME_DEF_STMT (op));
|
|
}
|
|
STRIP_NOPS (op);
|
|
}
|
|
done:
|
|
if (visited)
|
|
delete (visited);
|
|
return op;
|
|
}
|
|
|
|
/* Create polymorphic call context from IP invariant CST.
|
|
This is typically &global_var.
|
|
OTR_TYPE specify type of polymorphic call or NULL if unknown, OFF
|
|
is offset of call. */
|
|
|
|
ipa_polymorphic_call_context::ipa_polymorphic_call_context (tree cst,
|
|
tree otr_type,
|
|
HOST_WIDE_INT off)
|
|
{
|
|
clear_speculation ();
|
|
set_by_invariant (cst, otr_type, off);
|
|
}
|
|
|
|
/* Build context for pointer REF contained in FNDECL at statement STMT.
|
|
if INSTANCE is non-NULL, return pointer to the object described by
|
|
the context or DECL where context is contained in. */
|
|
|
|
ipa_polymorphic_call_context::ipa_polymorphic_call_context (tree fndecl,
|
|
tree ref,
|
|
gimple *stmt,
|
|
tree *instance)
|
|
{
|
|
tree otr_type = NULL;
|
|
tree base_pointer;
|
|
hash_set <tree> *visited = NULL;
|
|
|
|
if (TREE_CODE (ref) == OBJ_TYPE_REF)
|
|
{
|
|
otr_type = obj_type_ref_class (ref);
|
|
base_pointer = OBJ_TYPE_REF_OBJECT (ref);
|
|
}
|
|
else
|
|
base_pointer = ref;
|
|
|
|
/* Set up basic info in case we find nothing interesting in the analysis. */
|
|
clear_speculation ();
|
|
clear_outer_type (otr_type);
|
|
invalid = false;
|
|
|
|
/* Walk SSA for outer object. */
|
|
while (true)
|
|
{
|
|
base_pointer = walk_ssa_copies (base_pointer, &visited);
|
|
if (TREE_CODE (base_pointer) == ADDR_EXPR)
|
|
{
|
|
HOST_WIDE_INT offset2, size;
|
|
bool reverse;
|
|
tree base
|
|
= get_ref_base_and_extent_hwi (TREE_OPERAND (base_pointer, 0),
|
|
&offset2, &size, &reverse);
|
|
if (!base)
|
|
break;
|
|
|
|
combine_speculation_with (TYPE_MAIN_VARIANT (TREE_TYPE (base)),
|
|
offset + offset2,
|
|
true,
|
|
NULL /* Do not change outer type. */);
|
|
|
|
/* If this is a varying address, punt. */
|
|
if (TREE_CODE (base) == MEM_REF || DECL_P (base))
|
|
{
|
|
/* We found dereference of a pointer. Type of the pointer
|
|
and MEM_REF is meaningless, but we can look further. */
|
|
offset_int mem_offset;
|
|
if (TREE_CODE (base) == MEM_REF
|
|
&& mem_ref_offset (base).is_constant (&mem_offset))
|
|
{
|
|
offset_int o = mem_offset * BITS_PER_UNIT;
|
|
o += offset;
|
|
o += offset2;
|
|
if (!wi::fits_shwi_p (o))
|
|
break;
|
|
base_pointer = TREE_OPERAND (base, 0);
|
|
offset = o.to_shwi ();
|
|
outer_type = NULL;
|
|
}
|
|
/* We found base object. In this case the outer_type
|
|
is known. */
|
|
else if (DECL_P (base))
|
|
{
|
|
if (visited)
|
|
delete (visited);
|
|
/* Only type inconsistent programs can have otr_type that is
|
|
not part of outer type. */
|
|
if (otr_type
|
|
&& !contains_type_p (TREE_TYPE (base),
|
|
offset + offset2, otr_type))
|
|
{
|
|
invalid = true;
|
|
if (instance)
|
|
*instance = base_pointer;
|
|
return;
|
|
}
|
|
set_by_decl (base, offset + offset2);
|
|
if (outer_type && maybe_in_construction && stmt)
|
|
maybe_in_construction
|
|
= decl_maybe_in_construction_p (base,
|
|
outer_type,
|
|
stmt,
|
|
fndecl);
|
|
if (instance)
|
|
*instance = base;
|
|
return;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
else if (TREE_CODE (base_pointer) == POINTER_PLUS_EXPR
|
|
&& TREE_CODE (TREE_OPERAND (base_pointer, 1)) == INTEGER_CST)
|
|
{
|
|
offset_int o
|
|
= offset_int::from (wi::to_wide (TREE_OPERAND (base_pointer, 1)),
|
|
SIGNED);
|
|
o *= BITS_PER_UNIT;
|
|
o += offset;
|
|
if (!wi::fits_shwi_p (o))
|
|
break;
|
|
offset = o.to_shwi ();
|
|
base_pointer = TREE_OPERAND (base_pointer, 0);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (visited)
|
|
delete (visited);
|
|
|
|
/* Try to determine type of the outer object. */
|
|
if (TREE_CODE (base_pointer) == SSA_NAME
|
|
&& SSA_NAME_IS_DEFAULT_DEF (base_pointer)
|
|
&& TREE_CODE (SSA_NAME_VAR (base_pointer)) == PARM_DECL)
|
|
{
|
|
/* See if parameter is THIS pointer of a method. */
|
|
if (TREE_CODE (TREE_TYPE (fndecl)) == METHOD_TYPE
|
|
&& SSA_NAME_VAR (base_pointer) == DECL_ARGUMENTS (fndecl))
|
|
{
|
|
outer_type
|
|
= TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (base_pointer)));
|
|
cgraph_node *node = cgraph_node::get (current_function_decl);
|
|
gcc_assert (TREE_CODE (outer_type) == RECORD_TYPE
|
|
|| TREE_CODE (outer_type) == UNION_TYPE);
|
|
|
|
/* Handle the case we inlined into a thunk. In this case
|
|
thunk has THIS pointer of type bar, but it really receives
|
|
address to its base type foo which sits in bar at
|
|
0-thunk.fixed_offset. It starts with code that adds
|
|
think.fixed_offset to the pointer to compensate for this.
|
|
|
|
Because we walked all the way to the beginning of thunk, we now
|
|
see pointer &bar-thunk.fixed_offset and need to compensate
|
|
for it. */
|
|
thunk_info *info = thunk_info::get (node);
|
|
if (info && info->fixed_offset)
|
|
offset -= info->fixed_offset * BITS_PER_UNIT;
|
|
|
|
/* Dynamic casting has possibly upcasted the type
|
|
in the hierarchy. In this case outer type is less
|
|
informative than inner type and we should forget
|
|
about it. */
|
|
if ((otr_type
|
|
&& !contains_type_p (outer_type, offset,
|
|
otr_type))
|
|
|| !contains_polymorphic_type_p (outer_type)
|
|
/* If we compile thunk with virtual offset, the THIS pointer
|
|
is adjusted by unknown value. We can't thus use outer info
|
|
at all. */
|
|
|| (info && info->virtual_offset_p))
|
|
{
|
|
outer_type = NULL;
|
|
if (instance)
|
|
*instance = base_pointer;
|
|
return;
|
|
}
|
|
|
|
dynamic = true;
|
|
|
|
/* If the function is constructor or destructor, then
|
|
the type is possibly in construction, but we know
|
|
it is not derived type. */
|
|
if (DECL_CXX_CONSTRUCTOR_P (fndecl)
|
|
|| DECL_CXX_DESTRUCTOR_P (fndecl))
|
|
{
|
|
maybe_in_construction = true;
|
|
maybe_derived_type = false;
|
|
}
|
|
else
|
|
{
|
|
maybe_derived_type = true;
|
|
maybe_in_construction = false;
|
|
}
|
|
if (instance)
|
|
{
|
|
thunk_info *info = thunk_info::get (node);
|
|
/* If method is expanded thunk, we need to apply thunk offset
|
|
to instance pointer. */
|
|
if (info && (info->virtual_offset_p || info->fixed_offset))
|
|
*instance = NULL;
|
|
else
|
|
*instance = base_pointer;
|
|
}
|
|
return;
|
|
}
|
|
/* Non-PODs passed by value are really passed by invisible
|
|
reference. In this case we also know the type of the
|
|
object. */
|
|
if (DECL_BY_REFERENCE (SSA_NAME_VAR (base_pointer)))
|
|
{
|
|
outer_type
|
|
= TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (base_pointer)));
|
|
/* Only type inconsistent programs can have otr_type that is
|
|
not part of outer type. */
|
|
if (otr_type && !contains_type_p (outer_type, offset,
|
|
otr_type))
|
|
{
|
|
invalid = true;
|
|
if (instance)
|
|
*instance = base_pointer;
|
|
return;
|
|
}
|
|
/* Non-polymorphic types have no interest for us. */
|
|
else if (!otr_type && !contains_polymorphic_type_p (outer_type))
|
|
{
|
|
outer_type = NULL;
|
|
if (instance)
|
|
*instance = base_pointer;
|
|
return;
|
|
}
|
|
maybe_derived_type = false;
|
|
maybe_in_construction = false;
|
|
if (instance)
|
|
*instance = base_pointer;
|
|
return;
|
|
}
|
|
}
|
|
|
|
tree base_type = TREE_TYPE (base_pointer);
|
|
|
|
if (TREE_CODE (base_pointer) == SSA_NAME
|
|
&& SSA_NAME_IS_DEFAULT_DEF (base_pointer)
|
|
&& !(TREE_CODE (SSA_NAME_VAR (base_pointer)) == PARM_DECL
|
|
|| TREE_CODE (SSA_NAME_VAR (base_pointer)) == RESULT_DECL))
|
|
{
|
|
invalid = true;
|
|
if (instance)
|
|
*instance = base_pointer;
|
|
return;
|
|
}
|
|
if (TREE_CODE (base_pointer) == SSA_NAME
|
|
&& SSA_NAME_DEF_STMT (base_pointer)
|
|
&& gimple_assign_single_p (SSA_NAME_DEF_STMT (base_pointer)))
|
|
base_type = TREE_TYPE (gimple_assign_rhs1
|
|
(SSA_NAME_DEF_STMT (base_pointer)));
|
|
|
|
if (base_type && POINTER_TYPE_P (base_type))
|
|
combine_speculation_with (TYPE_MAIN_VARIANT (TREE_TYPE (base_type)),
|
|
offset,
|
|
true, NULL /* Do not change type here */);
|
|
/* TODO: There are multiple ways to derive a type. For instance
|
|
if BASE_POINTER is passed to an constructor call prior our reference.
|
|
We do not make this type of flow sensitive analysis yet. */
|
|
if (instance)
|
|
*instance = base_pointer;
|
|
return;
|
|
}
|
|
|
|
/* 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 nonzero if we possibly missed some dynamic type changes and we
|
|
should consider the set to be speculative. */
|
|
unsigned speculative;
|
|
|
|
/* 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;
|
|
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. */
|
|
|
|
static 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_ref_base_and_extent 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_ultimate_origin (block)) == FUNCTION_DECL)
|
|
return inlined_polymorphic_ctor_dtor_block_p (block, false);
|
|
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 if type changes
|
|
in unknown way or ERROR_MARK_NODE if type is unchanged. */
|
|
|
|
static tree
|
|
extr_type_from_vtbl_ptr_store (gimple *stmt, struct type_change_info *tci,
|
|
HOST_WIDE_INT *type_offset)
|
|
{
|
|
poly_int64 offset, size, max_size;
|
|
tree lhs, rhs, base;
|
|
bool reverse;
|
|
|
|
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)))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " LHS is not virtual table.\n");
|
|
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, &reverse);
|
|
if (DECL_P (tci->instance))
|
|
{
|
|
if (base != tci->instance)
|
|
{
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " base:");
|
|
print_generic_expr (dump_file, base, TDF_SLIM);
|
|
fprintf (dump_file, " does not match instance:");
|
|
print_generic_expr (dump_file, tci->instance, TDF_SLIM);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
return NULL_TREE;
|
|
}
|
|
}
|
|
else if (TREE_CODE (base) == MEM_REF)
|
|
{
|
|
if (!operand_equal_p (tci->instance, TREE_OPERAND (base, 0), 0))
|
|
{
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " base mem ref:");
|
|
print_generic_expr (dump_file, base, TDF_SLIM);
|
|
fprintf (dump_file, " does not match instance:");
|
|
print_generic_expr (dump_file, tci->instance, TDF_SLIM);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
return NULL_TREE;
|
|
}
|
|
if (!integer_zerop (TREE_OPERAND (base, 1)))
|
|
{
|
|
if (!tree_fits_shwi_p (TREE_OPERAND (base, 1)))
|
|
{
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " base mem ref:");
|
|
print_generic_expr (dump_file, base, TDF_SLIM);
|
|
fprintf (dump_file, " has non-representable offset:");
|
|
print_generic_expr (dump_file, tci->instance, TDF_SLIM);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
return NULL_TREE;
|
|
}
|
|
else
|
|
offset += tree_to_shwi (TREE_OPERAND (base, 1)) * BITS_PER_UNIT;
|
|
}
|
|
}
|
|
else if (!operand_equal_p (tci->instance, base, 0)
|
|
|| tci->offset)
|
|
{
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " base:");
|
|
print_generic_expr (dump_file, base, TDF_SLIM);
|
|
fprintf (dump_file, " does not match instance:");
|
|
print_generic_expr (dump_file, tci->instance, TDF_SLIM);
|
|
fprintf (dump_file, " with offset %i\n", (int)tci->offset);
|
|
}
|
|
return tci->offset > POINTER_SIZE ? error_mark_node : NULL_TREE;
|
|
}
|
|
if (maybe_ne (offset, tci->offset)
|
|
|| maybe_ne (size, POINTER_SIZE)
|
|
|| maybe_ne (max_size, POINTER_SIZE))
|
|
{
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " wrong offset ");
|
|
print_dec (offset, dump_file);
|
|
fprintf (dump_file, "!=%i or size ", (int) tci->offset);
|
|
print_dec (size, dump_file);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
return (known_le (offset + POINTER_SIZE, tci->offset)
|
|
|| (known_size_p (max_size)
|
|
&& known_gt (tci->offset + POINTER_SIZE,
|
|
offset + max_size))
|
|
? error_mark_node : NULL);
|
|
}
|
|
}
|
|
|
|
tree vtable;
|
|
unsigned HOST_WIDE_INT offset2;
|
|
|
|
if (!vtable_pointer_value_to_vtable (rhs, &vtable, &offset2))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " Failed to lookup binfo\n");
|
|
return NULL;
|
|
}
|
|
|
|
tree binfo = subbinfo_with_vtable_at_offset (TYPE_BINFO (DECL_CONTEXT (vtable)),
|
|
offset2, vtable);
|
|
if (!binfo)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " Construction vtable used\n");
|
|
/* FIXME: We should support construction contexts. */
|
|
return NULL;
|
|
}
|
|
|
|
*type_offset = tree_to_shwi (BINFO_OFFSET (binfo)) * BITS_PER_UNIT;
|
|
return DECL_CONTEXT (vtable);
|
|
}
|
|
|
|
/* Record dynamic type change of TCI to TYPE. */
|
|
|
|
static 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 we found a constructor of type that is not polymorphic or
|
|
that may contain the type in question as a field (not as base),
|
|
restrict to the inner class first to make type matching bellow
|
|
happier. */
|
|
if (type
|
|
&& (offset
|
|
|| (TREE_CODE (type) != RECORD_TYPE
|
|
|| !TYPE_BINFO (type)
|
|
|| !polymorphic_type_binfo_p (TYPE_BINFO (type)))))
|
|
{
|
|
ipa_polymorphic_call_context context;
|
|
|
|
context.offset = offset;
|
|
context.outer_type = type;
|
|
context.maybe_in_construction = false;
|
|
context.maybe_derived_type = false;
|
|
context.dynamic = true;
|
|
/* If we failed to find the inner type, we know that the call
|
|
would be undefined for type produced here. */
|
|
if (!context.restrict_to_inner_class (tci->otr_type))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " Ignoring; does not contain otr_type\n");
|
|
return;
|
|
}
|
|
/* Watch for case we reached an POD type and anticipate placement
|
|
new. */
|
|
if (!context.maybe_derived_type)
|
|
{
|
|
type = context.outer_type;
|
|
offset = context.offset;
|
|
}
|
|
}
|
|
if (tci->type_maybe_changed
|
|
&& (!types_same_for_odr (type, tci->known_current_type)
|
|
|| offset != tci->known_current_offset))
|
|
tci->multiple_types_encountered = true;
|
|
tci->known_current_type = TYPE_MAIN_VARIANT (type);
|
|
tci->known_current_offset = offset;
|
|
tci->type_maybe_changed = true;
|
|
}
|
|
|
|
|
|
/* The maximum number of may-defs we visit when looking for a must-def
|
|
that changes the dynamic type in check_stmt_for_type_change. Tuned
|
|
after the PR12392 testcase which unlimited spends 40% time within
|
|
these alias walks and 8% with the following limit. */
|
|
|
|
static inline bool
|
|
csftc_abort_walking_p (unsigned speculative)
|
|
{
|
|
unsigned max = param_max_speculative_devirt_maydefs;
|
|
return speculative > max ? true : false;
|
|
}
|
|
|
|
/* 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 = TYPE_METHOD_BASETYPE (TREE_TYPE (fn));
|
|
HOST_WIDE_INT offset = 0;
|
|
bool reverse;
|
|
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " Checking constructor call: ");
|
|
print_gimple_stmt (dump_file, stmt, 0);
|
|
}
|
|
|
|
/* See if THIS parameter seems like instance pointer. */
|
|
if (TREE_CODE (op) == ADDR_EXPR)
|
|
{
|
|
HOST_WIDE_INT size;
|
|
op = get_ref_base_and_extent_hwi (TREE_OPERAND (op, 0),
|
|
&offset, &size, &reverse);
|
|
if (!op)
|
|
{
|
|
tci->speculative++;
|
|
return csftc_abort_walking_p (tci->speculative);
|
|
}
|
|
if (TREE_CODE (op) == MEM_REF)
|
|
{
|
|
if (!tree_fits_shwi_p (TREE_OPERAND (op, 1)))
|
|
{
|
|
tci->speculative++;
|
|
return csftc_abort_walking_p (tci->speculative);
|
|
}
|
|
offset += tree_to_shwi (TREE_OPERAND (op, 1))
|
|
* BITS_PER_UNIT;
|
|
op = TREE_OPERAND (op, 0);
|
|
}
|
|
else if (DECL_P (op))
|
|
;
|
|
else
|
|
{
|
|
tci->speculative++;
|
|
return csftc_abort_walking_p (tci->speculative);
|
|
}
|
|
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
|
|
/* Some inlined constructors may look as follows:
|
|
_3 = operator new (16);
|
|
MEM[(struct &)_3] ={v} {CLOBBER};
|
|
MEM[(struct CompositeClass *)_3]._vptr.CompositeClass
|
|
= &MEM[(void *)&_ZTV14CompositeClass + 16B];
|
|
_7 = &MEM[(struct CompositeClass *)_3].object;
|
|
EmptyClass::EmptyClass (_7);
|
|
|
|
When determining dynamic type of _3 and because we stop at first
|
|
dynamic type found, we would stop on EmptyClass::EmptyClass (_7).
|
|
In this case the emptyclass is not even polymorphic and we miss
|
|
it is contained in an outer type that is polymorphic. */
|
|
|
|
&& (tci->offset == offset || contains_polymorphic_type_p (type)))
|
|
{
|
|
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);
|
|
}
|
|
tci->speculative++;
|
|
return csftc_abort_walking_p (tci->speculative);
|
|
}
|
|
/* 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);
|
|
}
|
|
|
|
type = extr_type_from_vtbl_ptr_store (stmt, tci, &offset);
|
|
if (type == error_mark_node)
|
|
return false;
|
|
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++;
|
|
}
|
|
else
|
|
record_known_type (tci, type, offset);
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/* THIS 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.
|
|
|
|
Return true if memory is unchanged from function entry.
|
|
|
|
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.
|
|
|
|
AA_WALK_BUDGET_P, if not NULL, is how statements we should allow
|
|
walk_aliased_vdefs to examine. The value should be decremented by the
|
|
number of statements we examined or set to zero if exhausted. */
|
|
|
|
bool
|
|
ipa_polymorphic_call_context::get_dynamic_type (tree instance,
|
|
tree otr_object,
|
|
tree otr_type,
|
|
gimple *call,
|
|
unsigned *aa_walk_budget_p)
|
|
{
|
|
struct type_change_info tci;
|
|
ao_ref ao;
|
|
bool function_entry_reached = false;
|
|
tree instance_ref = NULL;
|
|
gimple *stmt = call;
|
|
/* Remember OFFSET before it is modified by restrict_to_inner_class.
|
|
This is because we do not update INSTANCE when walking inwards. */
|
|
HOST_WIDE_INT instance_offset = offset;
|
|
tree instance_outer_type = outer_type;
|
|
|
|
if (!instance)
|
|
return false;
|
|
|
|
if (otr_type)
|
|
otr_type = TYPE_MAIN_VARIANT (otr_type);
|
|
|
|
/* Walk into inner type. This may clear maybe_derived_type and save us
|
|
from useless work. It also makes later comparisons with static type
|
|
easier. */
|
|
if (outer_type && otr_type)
|
|
{
|
|
if (!restrict_to_inner_class (otr_type))
|
|
return false;
|
|
}
|
|
|
|
if (!maybe_in_construction && !maybe_derived_type)
|
|
return false;
|
|
|
|
/* If we are in fact not looking at any object or the instance is
|
|
some placement new into a random load, give up straight away. */
|
|
if (TREE_CODE (instance) == MEM_REF)
|
|
return false;
|
|
|
|
/* We need to obtain reference 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);
|
|
bool reverse;
|
|
|
|
if (TREE_CODE (ref) == OBJ_TYPE_REF)
|
|
{
|
|
ref = OBJ_TYPE_REF_EXPR (ref);
|
|
ref = walk_ssa_copies (ref);
|
|
|
|
/* If call target is already known, no need to do the expensive
|
|
memory walk. */
|
|
if (is_gimple_min_invariant (ref))
|
|
return false;
|
|
|
|
/* 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)))
|
|
{
|
|
HOST_WIDE_INT offset2, size;
|
|
tree ref_exp = gimple_assign_rhs1 (SSA_NAME_DEF_STMT (ref));
|
|
tree base_ref
|
|
= get_ref_base_and_extent_hwi (ref_exp, &offset2,
|
|
&size, &reverse);
|
|
|
|
/* 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 == instance_offset
|
|
&& TREE_OPERAND (base_ref, 0) == instance)
|
|
|| (!offset2
|
|
&& TREE_OPERAND (base_ref, 0)
|
|
== otr_object)))
|
|
|| (DECL_P (instance) && base_ref == instance
|
|
&& offset2 == instance_offset)))
|
|
{
|
|
stmt = SSA_NAME_DEF_STMT (ref);
|
|
instance_ref = ref_exp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If we failed to look up the reference 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;
|
|
/* We are looking for stores to vptr pointer within the instance of
|
|
outer type.
|
|
TODO: The vptr pointer type is globally known, we probably should
|
|
keep it and do that even when otr_type is unknown. */
|
|
if (otr_type)
|
|
{
|
|
ao.base_alias_set
|
|
= get_alias_set (outer_type ? outer_type : otr_type);
|
|
ao.ref_alias_set
|
|
= get_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);
|
|
fprintf (dump_file, " Starting walk at: ");
|
|
print_gimple_stmt (dump_file, stmt, 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)instance_offset);
|
|
fprintf (dump_file, " vtbl reference: ");
|
|
print_generic_expr (dump_file, instance_ref, TDF_SLIM);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
|
|
tci.offset = instance_offset;
|
|
tci.instance = instance;
|
|
tci.vtbl_ptr_ref = instance_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 = 0;
|
|
tci.seen_unanalyzed_store = false;
|
|
|
|
unsigned aa_walk_budget = 0;
|
|
if (aa_walk_budget_p)
|
|
aa_walk_budget = *aa_walk_budget_p + 1;
|
|
|
|
int walked
|
|
= walk_aliased_vdefs (&ao, gimple_vuse (stmt), check_stmt_for_type_change,
|
|
&tci, NULL, &function_entry_reached, aa_walk_budget);
|
|
|
|
/* 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 cannot 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 (outer_type)
|
|
we can safely ignore tci.speculative that is set on calls and give up
|
|
only if there was dynamic type store that may affect given variable
|
|
(seen_unanalyzed_store) */
|
|
|
|
if (walked < 0)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " AA walk budget exhausted.\n");
|
|
*aa_walk_budget_p = 0;
|
|
return false;
|
|
}
|
|
else if (aa_walk_budget_p)
|
|
*aa_walk_budget_p -= walked;
|
|
|
|
if (!tci.type_maybe_changed
|
|
|| (outer_type
|
|
&& !dynamic
|
|
&& !tci.seen_unanalyzed_store
|
|
&& !tci.multiple_types_encountered
|
|
&& ((offset == tci.offset
|
|
&& types_same_for_odr (tci.known_current_type,
|
|
outer_type))
|
|
|| (instance_offset == offset
|
|
&& types_same_for_odr (tci.known_current_type,
|
|
instance_outer_type)))))
|
|
{
|
|
if (!outer_type || tci.seen_unanalyzed_store)
|
|
return false;
|
|
if (maybe_in_construction)
|
|
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)
|
|
{
|
|
outer_type = TYPE_MAIN_VARIANT (tci.known_current_type);
|
|
offset = tci.known_current_offset;
|
|
dynamic = true;
|
|
maybe_in_construction = false;
|
|
maybe_derived_type = false;
|
|
if (dump_file)
|
|
fprintf (dump_file, " Determined dynamic type.\n");
|
|
}
|
|
else if (!speculative_outer_type
|
|
|| speculative_maybe_derived_type)
|
|
{
|
|
speculative_outer_type = TYPE_MAIN_VARIANT (tci.known_current_type);
|
|
speculative_offset = tci.known_current_offset;
|
|
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%s%s\n",
|
|
function_entry_reached ? " (function entry reached)" : "",
|
|
function_entry_reached ? " (multiple types encountered)" : "");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* See if speculation given by SPEC_OUTER_TYPE, SPEC_OFFSET and SPEC_MAYBE_DERIVED_TYPE
|
|
seems consistent (and useful) with what we already have in the non-speculative context. */
|
|
|
|
bool
|
|
ipa_polymorphic_call_context::speculation_consistent_p (tree spec_outer_type,
|
|
HOST_WIDE_INT spec_offset,
|
|
bool spec_maybe_derived_type,
|
|
tree otr_type) const
|
|
{
|
|
if (!flag_devirtualize_speculatively)
|
|
return false;
|
|
|
|
/* Non-polymorphic types are useless for deriving likely polymorphic
|
|
call targets. */
|
|
if (!spec_outer_type || !contains_polymorphic_type_p (spec_outer_type))
|
|
return false;
|
|
|
|
/* If we know nothing, speculation is always good. */
|
|
if (!outer_type)
|
|
return true;
|
|
|
|
/* Speculation is only useful to avoid derived types.
|
|
This is not 100% true for placement new, where the outer context may
|
|
turn out to be useless, but ignore these for now. */
|
|
if (!maybe_derived_type)
|
|
return false;
|
|
|
|
/* If types agrees, speculation is consistent, but it makes sense only
|
|
when it says something new. */
|
|
if (types_must_be_same_for_odr (spec_outer_type, outer_type))
|
|
return maybe_derived_type && !spec_maybe_derived_type;
|
|
|
|
/* If speculation does not contain the type in question, ignore it. */
|
|
if (otr_type
|
|
&& !contains_type_p (spec_outer_type, spec_offset, otr_type, false, true))
|
|
return false;
|
|
|
|
/* If outer type already contains speculation as a filed,
|
|
it is useless. We already know from OUTER_TYPE
|
|
SPEC_TYPE and that it is not in the construction. */
|
|
if (contains_type_p (outer_type, offset - spec_offset,
|
|
spec_outer_type, false, false))
|
|
return false;
|
|
|
|
/* If speculative outer type is not more specified than outer
|
|
type, just give up.
|
|
We can only decide this safely if we can compare types with OUTER_TYPE.
|
|
*/
|
|
if ((!in_lto_p || odr_type_p (outer_type))
|
|
&& !contains_type_p (spec_outer_type,
|
|
spec_offset - offset,
|
|
outer_type, false))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/* Improve THIS with speculation described by NEW_OUTER_TYPE, NEW_OFFSET,
|
|
NEW_MAYBE_DERIVED_TYPE
|
|
If OTR_TYPE is set, assume the context is used with OTR_TYPE. */
|
|
|
|
bool
|
|
ipa_polymorphic_call_context::combine_speculation_with
|
|
(tree new_outer_type, HOST_WIDE_INT new_offset, bool new_maybe_derived_type,
|
|
tree otr_type)
|
|
{
|
|
if (!new_outer_type)
|
|
return false;
|
|
|
|
/* restrict_to_inner_class may eliminate wrong speculation making our job
|
|
easier. */
|
|
if (otr_type)
|
|
restrict_to_inner_class (otr_type);
|
|
|
|
if (!speculation_consistent_p (new_outer_type, new_offset,
|
|
new_maybe_derived_type, otr_type))
|
|
return false;
|
|
|
|
/* New speculation is a win in case we have no speculation or new
|
|
speculation does not consider derivations. */
|
|
if (!speculative_outer_type
|
|
|| (speculative_maybe_derived_type
|
|
&& !new_maybe_derived_type))
|
|
{
|
|
speculative_outer_type = new_outer_type;
|
|
speculative_offset = new_offset;
|
|
speculative_maybe_derived_type = new_maybe_derived_type;
|
|
return true;
|
|
}
|
|
else if (types_must_be_same_for_odr (speculative_outer_type,
|
|
new_outer_type))
|
|
{
|
|
if (speculative_offset != new_offset)
|
|
{
|
|
/* OK we have two contexts that seems valid but they disagree,
|
|
just give up.
|
|
|
|
This is not a lattice operation, so we may want to drop it later. */
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file,
|
|
"Speculative outer types match, "
|
|
"offset mismatch -> invalid speculation\n");
|
|
clear_speculation ();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (speculative_maybe_derived_type && !new_maybe_derived_type)
|
|
{
|
|
speculative_maybe_derived_type = false;
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
/* Choose type that contains the other. This one either contains the outer
|
|
as a field (thus giving exactly one target) or is deeper in the type
|
|
hierarchy. */
|
|
else if (speculative_outer_type
|
|
&& speculative_maybe_derived_type
|
|
&& (new_offset > speculative_offset
|
|
|| (new_offset == speculative_offset
|
|
&& contains_type_p (new_outer_type,
|
|
0, speculative_outer_type, false))))
|
|
{
|
|
tree old_outer_type = speculative_outer_type;
|
|
HOST_WIDE_INT old_offset = speculative_offset;
|
|
bool old_maybe_derived_type = speculative_maybe_derived_type;
|
|
|
|
speculative_outer_type = new_outer_type;
|
|
speculative_offset = new_offset;
|
|
speculative_maybe_derived_type = new_maybe_derived_type;
|
|
|
|
if (otr_type)
|
|
restrict_to_inner_class (otr_type);
|
|
|
|
/* If the speculation turned out to make no sense, revert to sensible
|
|
one. */
|
|
if (!speculative_outer_type)
|
|
{
|
|
speculative_outer_type = old_outer_type;
|
|
speculative_offset = old_offset;
|
|
speculative_maybe_derived_type = old_maybe_derived_type;
|
|
return false;
|
|
}
|
|
return (old_offset != speculative_offset
|
|
|| old_maybe_derived_type != speculative_maybe_derived_type
|
|
|| types_must_be_same_for_odr (speculative_outer_type,
|
|
new_outer_type));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Make speculation less specific so
|
|
NEW_OUTER_TYPE, NEW_OFFSET, NEW_MAYBE_DERIVED_TYPE is also included.
|
|
If OTR_TYPE is set, assume the context is used with OTR_TYPE. */
|
|
|
|
bool
|
|
ipa_polymorphic_call_context::meet_speculation_with
|
|
(tree new_outer_type, HOST_WIDE_INT new_offset, bool new_maybe_derived_type,
|
|
tree otr_type)
|
|
{
|
|
if (!new_outer_type && speculative_outer_type)
|
|
{
|
|
clear_speculation ();
|
|
return true;
|
|
}
|
|
|
|
/* restrict_to_inner_class may eliminate wrong speculation making our job
|
|
easier. */
|
|
if (otr_type)
|
|
restrict_to_inner_class (otr_type);
|
|
|
|
if (!speculative_outer_type
|
|
|| !speculation_consistent_p (speculative_outer_type,
|
|
speculative_offset,
|
|
speculative_maybe_derived_type,
|
|
otr_type))
|
|
return false;
|
|
|
|
if (!speculation_consistent_p (new_outer_type, new_offset,
|
|
new_maybe_derived_type, otr_type))
|
|
{
|
|
clear_speculation ();
|
|
return true;
|
|
}
|
|
|
|
else if (types_must_be_same_for_odr (speculative_outer_type,
|
|
new_outer_type))
|
|
{
|
|
if (speculative_offset != new_offset)
|
|
{
|
|
clear_speculation ();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (!speculative_maybe_derived_type && new_maybe_derived_type)
|
|
{
|
|
speculative_maybe_derived_type = true;
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
/* See if one type contains the other as a field (not base). */
|
|
else if (contains_type_p (new_outer_type, new_offset - speculative_offset,
|
|
speculative_outer_type, false, false))
|
|
return false;
|
|
else if (contains_type_p (speculative_outer_type,
|
|
speculative_offset - new_offset,
|
|
new_outer_type, false, false))
|
|
{
|
|
speculative_outer_type = new_outer_type;
|
|
speculative_offset = new_offset;
|
|
speculative_maybe_derived_type = new_maybe_derived_type;
|
|
return true;
|
|
}
|
|
/* See if OUTER_TYPE is base of CTX.OUTER_TYPE. */
|
|
else if (contains_type_p (new_outer_type,
|
|
new_offset - speculative_offset,
|
|
speculative_outer_type, false, true))
|
|
{
|
|
if (!speculative_maybe_derived_type)
|
|
{
|
|
speculative_maybe_derived_type = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/* See if CTX.OUTER_TYPE is base of OUTER_TYPE. */
|
|
else if (contains_type_p (speculative_outer_type,
|
|
speculative_offset - new_offset, new_outer_type, false, true))
|
|
{
|
|
speculative_outer_type = new_outer_type;
|
|
speculative_offset = new_offset;
|
|
speculative_maybe_derived_type = true;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Giving up on speculative meet\n");
|
|
clear_speculation ();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* Assume that both THIS and a given context is valid and strengthen THIS
|
|
if possible. Return true if any strengthening was made.
|
|
If actual type the context is being used in is known, OTR_TYPE should be
|
|
set accordingly. This improves quality of combined result. */
|
|
|
|
bool
|
|
ipa_polymorphic_call_context::combine_with (ipa_polymorphic_call_context ctx,
|
|
tree otr_type)
|
|
{
|
|
bool updated = false;
|
|
|
|
if (ctx.useless_p () || invalid)
|
|
return false;
|
|
|
|
/* Restricting context to inner type makes merging easier, however do not
|
|
do that unless we know how the context is used (OTR_TYPE is non-NULL) */
|
|
if (otr_type && !invalid && !ctx.invalid)
|
|
{
|
|
restrict_to_inner_class (otr_type);
|
|
ctx.restrict_to_inner_class (otr_type);
|
|
if(invalid)
|
|
return false;
|
|
}
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Polymorphic call context combine:");
|
|
dump (dump_file);
|
|
fprintf (dump_file, "With context: ");
|
|
ctx.dump (dump_file);
|
|
if (otr_type)
|
|
{
|
|
fprintf (dump_file, "To be used with type: ");
|
|
print_generic_expr (dump_file, otr_type, TDF_SLIM);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
}
|
|
|
|
/* If call is known to be invalid, we are done. */
|
|
if (ctx.invalid)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "-> Invalid context\n");
|
|
goto invalidate;
|
|
}
|
|
|
|
if (!ctx.outer_type)
|
|
;
|
|
else if (!outer_type)
|
|
{
|
|
outer_type = ctx.outer_type;
|
|
offset = ctx.offset;
|
|
dynamic = ctx.dynamic;
|
|
maybe_in_construction = ctx.maybe_in_construction;
|
|
maybe_derived_type = ctx.maybe_derived_type;
|
|
updated = true;
|
|
}
|
|
/* If types are known to be same, merging is quite easy. */
|
|
else if (types_must_be_same_for_odr (outer_type, ctx.outer_type))
|
|
{
|
|
if (offset != ctx.offset
|
|
&& TYPE_SIZE (outer_type)
|
|
&& TREE_CODE (TYPE_SIZE (outer_type)) == INTEGER_CST)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Outer types match, offset mismatch -> invalid\n");
|
|
clear_speculation ();
|
|
clear_outer_type ();
|
|
invalid = true;
|
|
return true;
|
|
}
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Outer types match, merging flags\n");
|
|
if (maybe_in_construction && !ctx.maybe_in_construction)
|
|
{
|
|
updated = true;
|
|
maybe_in_construction = false;
|
|
}
|
|
if (maybe_derived_type && !ctx.maybe_derived_type)
|
|
{
|
|
updated = true;
|
|
maybe_derived_type = false;
|
|
}
|
|
if (dynamic && !ctx.dynamic)
|
|
{
|
|
updated = true;
|
|
dynamic = false;
|
|
}
|
|
}
|
|
/* If we know the type precisely, there is not much to improve. */
|
|
else if (!maybe_derived_type && !maybe_in_construction
|
|
&& !ctx.maybe_derived_type && !ctx.maybe_in_construction)
|
|
{
|
|
/* It may be easy to check if second context permits the first
|
|
and set INVALID otherwise. This is not easy to do in general;
|
|
contains_type_p may return false negatives for non-comparable
|
|
types.
|
|
|
|
If OTR_TYPE is known, we however can expect that
|
|
restrict_to_inner_class should have discovered the same base
|
|
type. */
|
|
if (otr_type && !ctx.maybe_in_construction && !ctx.maybe_derived_type)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Contextes disagree -> invalid\n");
|
|
goto invalidate;
|
|
}
|
|
}
|
|
/* See if one type contains the other as a field (not base).
|
|
In this case we want to choose the wider type, because it contains
|
|
more information. */
|
|
else if (contains_type_p (ctx.outer_type, ctx.offset - offset,
|
|
outer_type, false, false))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Second type contain the first as a field\n");
|
|
|
|
if (maybe_derived_type)
|
|
{
|
|
outer_type = ctx.outer_type;
|
|
maybe_derived_type = ctx.maybe_derived_type;
|
|
offset = ctx.offset;
|
|
dynamic = ctx.dynamic;
|
|
updated = true;
|
|
}
|
|
|
|
/* If we do not know how the context is being used, we cannot
|
|
clear MAYBE_IN_CONSTRUCTION because it may be offseted
|
|
to other component of OUTER_TYPE later and we know nothing
|
|
about it. */
|
|
if (otr_type && maybe_in_construction
|
|
&& !ctx.maybe_in_construction)
|
|
{
|
|
maybe_in_construction = false;
|
|
updated = true;
|
|
}
|
|
}
|
|
else if (contains_type_p (outer_type, offset - ctx.offset,
|
|
ctx.outer_type, false, false))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "First type contain the second as a field\n");
|
|
|
|
if (otr_type && maybe_in_construction
|
|
&& !ctx.maybe_in_construction)
|
|
{
|
|
maybe_in_construction = false;
|
|
updated = true;
|
|
}
|
|
}
|
|
/* See if OUTER_TYPE is base of CTX.OUTER_TYPE. */
|
|
else if (contains_type_p (ctx.outer_type,
|
|
ctx.offset - offset, outer_type, false, true))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "First type is base of second\n");
|
|
if (!maybe_derived_type)
|
|
{
|
|
if (!ctx.maybe_in_construction
|
|
&& types_odr_comparable (outer_type, ctx.outer_type))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Second context does not permit base -> invalid\n");
|
|
goto invalidate;
|
|
}
|
|
}
|
|
/* Pick variant deeper in the hierarchy. */
|
|
else
|
|
{
|
|
outer_type = ctx.outer_type;
|
|
maybe_in_construction = ctx.maybe_in_construction;
|
|
maybe_derived_type = ctx.maybe_derived_type;
|
|
offset = ctx.offset;
|
|
dynamic = ctx.dynamic;
|
|
updated = true;
|
|
}
|
|
}
|
|
/* See if CTX.OUTER_TYPE is base of OUTER_TYPE. */
|
|
else if (contains_type_p (outer_type,
|
|
offset - ctx.offset, ctx.outer_type, false, true))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Second type is base of first\n");
|
|
if (!ctx.maybe_derived_type)
|
|
{
|
|
if (!maybe_in_construction
|
|
&& types_odr_comparable (outer_type, ctx.outer_type))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "First context does not permit base -> invalid\n");
|
|
goto invalidate;
|
|
}
|
|
/* Pick the base type. */
|
|
else if (maybe_in_construction)
|
|
{
|
|
outer_type = ctx.outer_type;
|
|
maybe_in_construction = ctx.maybe_in_construction;
|
|
maybe_derived_type = ctx.maybe_derived_type;
|
|
offset = ctx.offset;
|
|
dynamic = ctx.dynamic;
|
|
updated = true;
|
|
}
|
|
}
|
|
}
|
|
/* TODO handle merging using hierarchy. */
|
|
else if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Giving up on merge\n");
|
|
|
|
updated |= combine_speculation_with (ctx.speculative_outer_type,
|
|
ctx.speculative_offset,
|
|
ctx.speculative_maybe_derived_type,
|
|
otr_type);
|
|
|
|
if (updated && dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Updated as: ");
|
|
dump (dump_file);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
return updated;
|
|
|
|
invalidate:
|
|
invalid = true;
|
|
clear_speculation ();
|
|
clear_outer_type ();
|
|
return true;
|
|
}
|
|
|
|
/* Take non-speculative info, merge it with speculative and clear speculation.
|
|
Used when we no longer manage to keep track of actual outer type, but we
|
|
think it is still there.
|
|
|
|
If OTR_TYPE is set, the transformation can be done more effectively assuming
|
|
that context is going to be used only that way. */
|
|
|
|
void
|
|
ipa_polymorphic_call_context::make_speculative (tree otr_type)
|
|
{
|
|
tree spec_outer_type = outer_type;
|
|
HOST_WIDE_INT spec_offset = offset;
|
|
bool spec_maybe_derived_type = maybe_derived_type;
|
|
|
|
if (invalid)
|
|
{
|
|
invalid = false;
|
|
clear_outer_type ();
|
|
clear_speculation ();
|
|
return;
|
|
}
|
|
if (!outer_type)
|
|
return;
|
|
clear_outer_type ();
|
|
combine_speculation_with (spec_outer_type, spec_offset,
|
|
spec_maybe_derived_type,
|
|
otr_type);
|
|
}
|
|
|
|
/* Use when we cannot track dynamic type change. This speculatively assume
|
|
type change is not happening. */
|
|
|
|
void
|
|
ipa_polymorphic_call_context::possible_dynamic_type_change (bool in_poly_cdtor,
|
|
tree otr_type)
|
|
{
|
|
if (dynamic)
|
|
make_speculative (otr_type);
|
|
else if (in_poly_cdtor)
|
|
maybe_in_construction = true;
|
|
}
|
|
|
|
/* Return TRUE if this context conveys the same information as OTHER. */
|
|
|
|
bool
|
|
ipa_polymorphic_call_context::equal_to
|
|
(const ipa_polymorphic_call_context &x) const
|
|
{
|
|
if (useless_p ())
|
|
return x.useless_p ();
|
|
if (invalid)
|
|
return x.invalid;
|
|
if (x.useless_p () || x.invalid)
|
|
return false;
|
|
|
|
if (outer_type)
|
|
{
|
|
if (!x.outer_type
|
|
|| !types_odr_comparable (outer_type, x.outer_type)
|
|
|| !types_same_for_odr (outer_type, x.outer_type)
|
|
|| offset != x.offset
|
|
|| maybe_in_construction != x.maybe_in_construction
|
|
|| maybe_derived_type != x.maybe_derived_type
|
|
|| dynamic != x.dynamic)
|
|
return false;
|
|
}
|
|
else if (x.outer_type)
|
|
return false;
|
|
|
|
|
|
if (speculative_outer_type
|
|
&& speculation_consistent_p (speculative_outer_type, speculative_offset,
|
|
speculative_maybe_derived_type, NULL_TREE))
|
|
{
|
|
if (!x.speculative_outer_type)
|
|
return false;
|
|
|
|
if (!types_odr_comparable (speculative_outer_type,
|
|
x.speculative_outer_type)
|
|
|| !types_same_for_odr (speculative_outer_type,
|
|
x.speculative_outer_type)
|
|
|| speculative_offset != x.speculative_offset
|
|
|| speculative_maybe_derived_type != x.speculative_maybe_derived_type)
|
|
return false;
|
|
}
|
|
else if (x.speculative_outer_type
|
|
&& x.speculation_consistent_p (x.speculative_outer_type,
|
|
x.speculative_offset,
|
|
x.speculative_maybe_derived_type,
|
|
NULL))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Modify context to be strictly less restrictive than CTX. */
|
|
|
|
bool
|
|
ipa_polymorphic_call_context::meet_with (ipa_polymorphic_call_context ctx,
|
|
tree otr_type)
|
|
{
|
|
bool updated = false;
|
|
|
|
if (useless_p () || ctx.invalid)
|
|
return false;
|
|
|
|
/* Restricting context to inner type makes merging easier, however do not
|
|
do that unless we know how the context is used (OTR_TYPE is non-NULL) */
|
|
if (otr_type && !useless_p () && !ctx.useless_p ())
|
|
{
|
|
restrict_to_inner_class (otr_type);
|
|
ctx.restrict_to_inner_class (otr_type);
|
|
if(invalid)
|
|
return false;
|
|
}
|
|
|
|
if (equal_to (ctx))
|
|
return false;
|
|
|
|
if (ctx.useless_p () || invalid)
|
|
{
|
|
*this = ctx;
|
|
return true;
|
|
}
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Polymorphic call context meet:");
|
|
dump (dump_file);
|
|
fprintf (dump_file, "With context: ");
|
|
ctx.dump (dump_file);
|
|
if (otr_type)
|
|
{
|
|
fprintf (dump_file, "To be used with type: ");
|
|
print_generic_expr (dump_file, otr_type, TDF_SLIM);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
}
|
|
|
|
if (!dynamic && ctx.dynamic)
|
|
{
|
|
dynamic = true;
|
|
updated = true;
|
|
}
|
|
|
|
/* If call is known to be invalid, we are done. */
|
|
if (!outer_type)
|
|
;
|
|
else if (!ctx.outer_type)
|
|
{
|
|
clear_outer_type ();
|
|
updated = true;
|
|
}
|
|
/* If types are known to be same, merging is quite easy. */
|
|
else if (types_must_be_same_for_odr (outer_type, ctx.outer_type))
|
|
{
|
|
if (offset != ctx.offset
|
|
&& TYPE_SIZE (outer_type)
|
|
&& TREE_CODE (TYPE_SIZE (outer_type)) == INTEGER_CST)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Outer types match, offset mismatch -> clearing\n");
|
|
clear_outer_type ();
|
|
return true;
|
|
}
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Outer types match, merging flags\n");
|
|
if (!maybe_in_construction && ctx.maybe_in_construction)
|
|
{
|
|
updated = true;
|
|
maybe_in_construction = true;
|
|
}
|
|
if (!maybe_derived_type && ctx.maybe_derived_type)
|
|
{
|
|
updated = true;
|
|
maybe_derived_type = true;
|
|
}
|
|
if (!dynamic && ctx.dynamic)
|
|
{
|
|
updated = true;
|
|
dynamic = true;
|
|
}
|
|
}
|
|
/* See if one type contains the other as a field (not base). */
|
|
else if (contains_type_p (ctx.outer_type, ctx.offset - offset,
|
|
outer_type, false, false))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Second type contain the first as a field\n");
|
|
|
|
/* The second type is more specified, so we keep the first.
|
|
We need to set DYNAMIC flag to avoid declaring context INVALID
|
|
of OFFSET ends up being out of range. */
|
|
if (!dynamic
|
|
&& (ctx.dynamic
|
|
|| (!otr_type
|
|
&& (!TYPE_SIZE (ctx.outer_type)
|
|
|| !TYPE_SIZE (outer_type)
|
|
|| !operand_equal_p (TYPE_SIZE (ctx.outer_type),
|
|
TYPE_SIZE (outer_type), 0)))))
|
|
{
|
|
dynamic = true;
|
|
updated = true;
|
|
}
|
|
}
|
|
else if (contains_type_p (outer_type, offset - ctx.offset,
|
|
ctx.outer_type, false, false))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "First type contain the second as a field\n");
|
|
|
|
if (!dynamic
|
|
&& (ctx.dynamic
|
|
|| (!otr_type
|
|
&& (!TYPE_SIZE (ctx.outer_type)
|
|
|| !TYPE_SIZE (outer_type)
|
|
|| !operand_equal_p (TYPE_SIZE (ctx.outer_type),
|
|
TYPE_SIZE (outer_type), 0)))))
|
|
dynamic = true;
|
|
outer_type = ctx.outer_type;
|
|
offset = ctx.offset;
|
|
dynamic = ctx.dynamic;
|
|
maybe_in_construction = ctx.maybe_in_construction;
|
|
maybe_derived_type = ctx.maybe_derived_type;
|
|
updated = true;
|
|
}
|
|
/* See if OUTER_TYPE is base of CTX.OUTER_TYPE. */
|
|
else if (contains_type_p (ctx.outer_type,
|
|
ctx.offset - offset, outer_type, false, true))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "First type is base of second\n");
|
|
if (!maybe_derived_type)
|
|
{
|
|
maybe_derived_type = true;
|
|
updated = true;
|
|
}
|
|
if (!maybe_in_construction && ctx.maybe_in_construction)
|
|
{
|
|
maybe_in_construction = true;
|
|
updated = true;
|
|
}
|
|
if (!dynamic && ctx.dynamic)
|
|
{
|
|
dynamic = true;
|
|
updated = true;
|
|
}
|
|
}
|
|
/* See if CTX.OUTER_TYPE is base of OUTER_TYPE. */
|
|
else if (contains_type_p (outer_type,
|
|
offset - ctx.offset, ctx.outer_type, false, true))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Second type is base of first\n");
|
|
outer_type = ctx.outer_type;
|
|
offset = ctx.offset;
|
|
updated = true;
|
|
if (!maybe_derived_type)
|
|
maybe_derived_type = true;
|
|
if (!maybe_in_construction && ctx.maybe_in_construction)
|
|
maybe_in_construction = true;
|
|
if (!dynamic && ctx.dynamic)
|
|
dynamic = true;
|
|
}
|
|
/* TODO handle merging using hierarchy. */
|
|
else
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Giving up on meet\n");
|
|
clear_outer_type ();
|
|
updated = true;
|
|
}
|
|
|
|
updated |= meet_speculation_with (ctx.speculative_outer_type,
|
|
ctx.speculative_offset,
|
|
ctx.speculative_maybe_derived_type,
|
|
otr_type);
|
|
|
|
if (updated && dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Updated as: ");
|
|
dump (dump_file);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
return updated;
|
|
}
|