| /* Analysis of polymorphic call context. |
| Copyright (C) 2013-2021 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; |
| off = 0; |
| 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 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; |
| } |