2401 lines
73 KiB
C++
2401 lines
73 KiB
C++
/* Manipulation of formal and actual parameters of functions and function
|
|
calls.
|
|
Copyright (C) 2017-2022 Free Software Foundation, Inc.
|
|
|
|
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 "tree.h"
|
|
#include "gimple.h"
|
|
#include "ssa.h"
|
|
#include "cgraph.h"
|
|
#include "fold-const.h"
|
|
#include "tree-eh.h"
|
|
#include "stor-layout.h"
|
|
#include "gimplify.h"
|
|
#include "gimple-iterator.h"
|
|
#include "gimplify-me.h"
|
|
#include "tree-cfg.h"
|
|
#include "tree-dfa.h"
|
|
#include "ipa-param-manipulation.h"
|
|
#include "print-tree.h"
|
|
#include "gimple-pretty-print.h"
|
|
#include "builtins.h"
|
|
#include "tree-ssa.h"
|
|
#include "tree-inline.h"
|
|
#include "alloc-pool.h"
|
|
#include "symbol-summary.h"
|
|
#include "symtab-clones.h"
|
|
#include "tree-phinodes.h"
|
|
#include "cfgexpand.h"
|
|
#include "attribs.h"
|
|
|
|
|
|
/* Actual prefixes of different newly synthetized parameters. Keep in sync
|
|
with IPA_PARAM_PREFIX_* defines. */
|
|
|
|
static const char *ipa_param_prefixes[IPA_PARAM_PREFIX_COUNT]
|
|
= {"SYNTH",
|
|
"ISRA",
|
|
"simd",
|
|
"mask"};
|
|
|
|
/* Names of parameters for dumping. Keep in sync with enum ipa_parm_op. */
|
|
|
|
static const char *ipa_param_op_names[IPA_PARAM_PREFIX_COUNT]
|
|
= {"IPA_PARAM_OP_UNDEFINED",
|
|
"IPA_PARAM_OP_COPY",
|
|
"IPA_PARAM_OP_NEW",
|
|
"IPA_PARAM_OP_SPLIT"};
|
|
|
|
/* Structure to hold declarations representing pass-through IPA-SRA splits. In
|
|
essence, it tells new index for a combination of original index and
|
|
offset. */
|
|
|
|
struct pass_through_split_map
|
|
{
|
|
/* Original argument index. */
|
|
unsigned base_index;
|
|
/* Offset of the split part in the original argument. */
|
|
unsigned unit_offset;
|
|
/* Index of the split part in the call statement - where clone
|
|
materialization put it. */
|
|
int new_index;
|
|
};
|
|
|
|
/* Information about some call statements that needs to be conveyed from clone
|
|
materialization to edge redirection. */
|
|
|
|
class ipa_edge_modification_info
|
|
{
|
|
public:
|
|
ipa_edge_modification_info ()
|
|
{}
|
|
|
|
/* Mapping of original argument indices to where those arguments sit in the
|
|
call statement now or to a negative index if they were removed. */
|
|
auto_vec<int> index_map;
|
|
/* Information about ISRA replacements put into the call statement at the
|
|
clone materialization stages. */
|
|
auto_vec<pass_through_split_map> pass_through_map;
|
|
/* Necessary adjustment to ipa_param_adjustments::m_always_copy_start when
|
|
redirecting the call. */
|
|
int always_copy_delta = 0;
|
|
};
|
|
|
|
/* Class for storing and retrieving summaries about cal statement
|
|
modifications. */
|
|
|
|
class ipa_edge_modification_sum
|
|
: public call_summary <ipa_edge_modification_info *>
|
|
{
|
|
public:
|
|
ipa_edge_modification_sum (symbol_table *table)
|
|
: call_summary<ipa_edge_modification_info *> (table)
|
|
{
|
|
}
|
|
|
|
/* Hook that is called by summary when an edge is duplicated. */
|
|
|
|
virtual void duplicate (cgraph_edge *,
|
|
cgraph_edge *,
|
|
ipa_edge_modification_info *old_info,
|
|
ipa_edge_modification_info *new_info)
|
|
{
|
|
new_info->index_map.safe_splice (old_info->index_map);
|
|
new_info->pass_through_map.safe_splice (old_info->pass_through_map);
|
|
new_info->always_copy_delta = old_info->always_copy_delta;
|
|
}
|
|
};
|
|
|
|
/* Call summary to store information about edges which have had their arguments
|
|
partially modified already. */
|
|
|
|
static ipa_edge_modification_sum *ipa_edge_modifications;
|
|
|
|
/* Fail compilation if CS has any summary associated with it in
|
|
ipa_edge_modifications. */
|
|
|
|
DEBUG_FUNCTION void
|
|
ipa_verify_edge_has_no_modifications (cgraph_edge *cs)
|
|
{
|
|
gcc_assert (!ipa_edge_modifications || !ipa_edge_modifications->get (cs));
|
|
}
|
|
|
|
/* Fill an empty vector ARGS with PARM_DECLs representing formal parameters of
|
|
FNDECL. The function should not be called during LTO WPA phase except for
|
|
thunks (or functions with bodies streamed in). */
|
|
|
|
void
|
|
push_function_arg_decls (vec<tree> *args, tree fndecl)
|
|
{
|
|
int count;
|
|
tree parm;
|
|
|
|
/* Safety check that we do not attempt to use the function in WPA, except
|
|
when the function is a thunk and then we have DECL_ARGUMENTS or when we
|
|
have already explicitely loaded its body. */
|
|
gcc_assert (!flag_wpa
|
|
|| DECL_ARGUMENTS (fndecl)
|
|
|| gimple_has_body_p (fndecl));
|
|
count = 0;
|
|
for (parm = DECL_ARGUMENTS (fndecl); parm; parm = DECL_CHAIN (parm))
|
|
count++;
|
|
|
|
args->reserve_exact (count);
|
|
for (parm = DECL_ARGUMENTS (fndecl); parm; parm = DECL_CHAIN (parm))
|
|
args->quick_push (parm);
|
|
}
|
|
|
|
/* Fill an empty vector TYPES with trees representing formal parameters of
|
|
function type FNTYPE. */
|
|
|
|
void
|
|
push_function_arg_types (vec<tree> *types, tree fntype)
|
|
{
|
|
int count = 0;
|
|
tree t;
|
|
|
|
for (t = TYPE_ARG_TYPES (fntype); t; t = TREE_CHAIN (t))
|
|
count++;
|
|
|
|
types->reserve_exact (count);
|
|
for (t = TYPE_ARG_TYPES (fntype); t; t = TREE_CHAIN (t))
|
|
types->quick_push (TREE_VALUE (t));
|
|
}
|
|
|
|
/* Dump the adjustments in the vector ADJUSTMENTS to dump_file in a human
|
|
friendly way, assuming they are meant to be applied to FNDECL. */
|
|
|
|
void
|
|
ipa_dump_adjusted_parameters (FILE *f,
|
|
vec<ipa_adjusted_param, va_gc> *adj_params)
|
|
{
|
|
unsigned i, len = vec_safe_length (adj_params);
|
|
bool first = true;
|
|
|
|
if (!len)
|
|
return;
|
|
|
|
fprintf (f, " IPA adjusted parameters: ");
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
struct ipa_adjusted_param *apm;
|
|
apm = &(*adj_params)[i];
|
|
|
|
if (!first)
|
|
fprintf (f, " ");
|
|
else
|
|
first = false;
|
|
|
|
fprintf (f, "%i. %s %s", i, ipa_param_op_names[apm->op],
|
|
apm->prev_clone_adjustment ? "prev_clone_adjustment " : "");
|
|
switch (apm->op)
|
|
{
|
|
case IPA_PARAM_OP_UNDEFINED:
|
|
break;
|
|
|
|
case IPA_PARAM_OP_COPY:
|
|
fprintf (f, ", base_index: %u", apm->base_index);
|
|
fprintf (f, ", prev_clone_index: %u", apm->prev_clone_index);
|
|
break;
|
|
|
|
case IPA_PARAM_OP_SPLIT:
|
|
fprintf (f, ", offset: %u", apm->unit_offset);
|
|
/* fall-through */
|
|
case IPA_PARAM_OP_NEW:
|
|
fprintf (f, ", base_index: %u", apm->base_index);
|
|
fprintf (f, ", prev_clone_index: %u", apm->prev_clone_index);
|
|
print_node_brief (f, ", type: ", apm->type, 0);
|
|
print_node_brief (f, ", alias type: ", apm->alias_ptr_type, 0);
|
|
fprintf (f, " prefix: %s",
|
|
ipa_param_prefixes[apm->param_prefix_index]);
|
|
if (apm->reverse)
|
|
fprintf (f, ", reverse");
|
|
break;
|
|
}
|
|
fprintf (f, "\n");
|
|
}
|
|
}
|
|
|
|
/* Fill NEW_TYPES with types of a function after its current OTYPES have been
|
|
modified as described in ADJ_PARAMS. When USE_PREV_INDICES is true, use
|
|
prev_clone_index from ADJ_PARAMS as opposed to base_index when the parameter
|
|
is false. */
|
|
|
|
static void
|
|
fill_vector_of_new_param_types (vec<tree> *new_types, vec<tree> *otypes,
|
|
vec<ipa_adjusted_param, va_gc> *adj_params,
|
|
bool use_prev_indices)
|
|
{
|
|
unsigned adj_len = vec_safe_length (adj_params);
|
|
new_types->reserve_exact (adj_len);
|
|
for (unsigned i = 0; i < adj_len ; i++)
|
|
{
|
|
ipa_adjusted_param *apm = &(*adj_params)[i];
|
|
if (apm->op == IPA_PARAM_OP_COPY)
|
|
{
|
|
unsigned index
|
|
= use_prev_indices ? apm->prev_clone_index : apm->base_index;
|
|
/* The following needs to be handled gracefully because of type
|
|
mismatches. This happens with LTO but apparently also in Fortran
|
|
with -fcoarray=lib -O2 -lcaf_single -latomic. */
|
|
if (index >= otypes->length ())
|
|
continue;
|
|
new_types->quick_push ((*otypes)[index]);
|
|
}
|
|
else if (apm->op == IPA_PARAM_OP_NEW
|
|
|| apm->op == IPA_PARAM_OP_SPLIT)
|
|
{
|
|
tree ntype = apm->type;
|
|
if (is_gimple_reg_type (ntype)
|
|
&& TYPE_MODE (ntype) != BLKmode)
|
|
{
|
|
unsigned malign = GET_MODE_ALIGNMENT (TYPE_MODE (ntype));
|
|
if (TYPE_ALIGN (ntype) != malign)
|
|
ntype = build_aligned_type (ntype, malign);
|
|
}
|
|
new_types->quick_push (ntype);
|
|
}
|
|
else
|
|
gcc_unreachable ();
|
|
}
|
|
}
|
|
|
|
/* Return false if given attribute should prevent type adjustments. */
|
|
|
|
bool
|
|
ipa_param_adjustments::type_attribute_allowed_p (tree name)
|
|
{
|
|
if ((is_attribute_p ("fn spec", name) && flag_ipa_modref)
|
|
|| is_attribute_p ("access", name)
|
|
|| is_attribute_p ("returns_nonnull", name)
|
|
|| is_attribute_p ("assume_aligned", name)
|
|
|| is_attribute_p ("nocf_check", name)
|
|
|| is_attribute_p ("warn_unused_result", name))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Return true if attribute should be dropped if parameter changed. */
|
|
|
|
static bool
|
|
drop_type_attribute_if_params_changed_p (tree name)
|
|
{
|
|
if (is_attribute_p ("fn spec", name)
|
|
|| is_attribute_p ("access", name))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Build and return a function type just like ORIG_TYPE but with parameter
|
|
types given in NEW_PARAM_TYPES - which can be NULL if, but only if,
|
|
ORIG_TYPE itself has NULL TREE_ARG_TYPEs. If METHOD2FUNC is true, also make
|
|
it a FUNCTION_TYPE instead of FUNCTION_TYPE.
|
|
If ARG_MODIFIED is true drop attributes that are no longer up to date. */
|
|
|
|
static tree
|
|
build_adjusted_function_type (tree orig_type, vec<tree> *new_param_types,
|
|
bool method2func, bool skip_return,
|
|
bool args_modified)
|
|
{
|
|
tree new_arg_types = NULL;
|
|
if (TYPE_ARG_TYPES (orig_type))
|
|
{
|
|
gcc_checking_assert (new_param_types);
|
|
bool last_parm_void = (TREE_VALUE (tree_last (TYPE_ARG_TYPES (orig_type)))
|
|
== void_type_node);
|
|
unsigned len = new_param_types->length ();
|
|
for (unsigned i = 0; i < len; i++)
|
|
new_arg_types = tree_cons (NULL_TREE, (*new_param_types)[i],
|
|
new_arg_types);
|
|
|
|
tree new_reversed = nreverse (new_arg_types);
|
|
if (last_parm_void)
|
|
{
|
|
if (new_reversed)
|
|
TREE_CHAIN (new_arg_types) = void_list_node;
|
|
else
|
|
new_reversed = void_list_node;
|
|
}
|
|
new_arg_types = new_reversed;
|
|
}
|
|
|
|
/* Use build_distinct_type_copy to preserve as much as possible from original
|
|
type (debug info, attribute lists etc.). The one exception is
|
|
METHOD_TYPEs which must have THIS argument and when we are asked to remove
|
|
it, we need to build new FUNCTION_TYPE instead. */
|
|
tree new_type = NULL;
|
|
if (method2func)
|
|
{
|
|
tree ret_type;
|
|
if (skip_return)
|
|
ret_type = void_type_node;
|
|
else
|
|
ret_type = TREE_TYPE (orig_type);
|
|
|
|
new_type
|
|
= build_distinct_type_copy (build_function_type (ret_type,
|
|
new_arg_types));
|
|
TYPE_CONTEXT (new_type) = TYPE_CONTEXT (orig_type);
|
|
}
|
|
else
|
|
{
|
|
new_type = build_distinct_type_copy (orig_type);
|
|
TYPE_ARG_TYPES (new_type) = new_arg_types;
|
|
if (skip_return)
|
|
TREE_TYPE (new_type) = void_type_node;
|
|
}
|
|
if (args_modified && TYPE_ATTRIBUTES (new_type))
|
|
{
|
|
tree t = TYPE_ATTRIBUTES (new_type);
|
|
tree *last = &TYPE_ATTRIBUTES (new_type);
|
|
TYPE_ATTRIBUTES (new_type) = NULL;
|
|
for (;t; t = TREE_CHAIN (t))
|
|
if (!drop_type_attribute_if_params_changed_p
|
|
(get_attribute_name (t)))
|
|
{
|
|
*last = copy_node (t);
|
|
TREE_CHAIN (*last) = NULL;
|
|
last = &TREE_CHAIN (*last);
|
|
}
|
|
}
|
|
|
|
return new_type;
|
|
}
|
|
|
|
/* Return the maximum index in any IPA_PARAM_OP_COPY adjustment or -1 if there
|
|
is none. */
|
|
|
|
int
|
|
ipa_param_adjustments::get_max_base_index ()
|
|
{
|
|
unsigned adj_len = vec_safe_length (m_adj_params);
|
|
int max_index = -1;
|
|
for (unsigned i = 0; i < adj_len ; i++)
|
|
{
|
|
ipa_adjusted_param *apm = &(*m_adj_params)[i];
|
|
if (apm->op == IPA_PARAM_OP_COPY
|
|
&& max_index < apm->base_index)
|
|
max_index = apm->base_index;
|
|
}
|
|
return max_index;
|
|
}
|
|
|
|
|
|
/* Fill SURVIVING_PARAMS with an array of bools where each one says whether a
|
|
parameter that originally was at that position still survives in the given
|
|
clone or is removed/replaced. If the final array is smaller than an index
|
|
of an original parameter, that parameter also did not survive. That a
|
|
parameter survives does not mean it has the same index as before. */
|
|
|
|
void
|
|
ipa_param_adjustments::get_surviving_params (vec<bool> *surviving_params)
|
|
{
|
|
unsigned adj_len = vec_safe_length (m_adj_params);
|
|
int max_index = get_max_base_index ();
|
|
|
|
if (max_index < 0)
|
|
return;
|
|
surviving_params->reserve_exact (max_index + 1);
|
|
surviving_params->quick_grow_cleared (max_index + 1);
|
|
for (unsigned i = 0; i < adj_len ; i++)
|
|
{
|
|
ipa_adjusted_param *apm = &(*m_adj_params)[i];
|
|
if (apm->op == IPA_PARAM_OP_COPY)
|
|
(*surviving_params)[apm->base_index] = true;
|
|
}
|
|
}
|
|
|
|
/* Fill NEW_INDICES with new indices of each surviving parameter or -1 for
|
|
those which do not survive. Any parameter outside of lenght of the vector
|
|
does not survive. There is currently no support for a parameter to be
|
|
copied to two distinct new parameters. */
|
|
|
|
void
|
|
ipa_param_adjustments::get_updated_indices (vec<int> *new_indices)
|
|
{
|
|
unsigned adj_len = vec_safe_length (m_adj_params);
|
|
int max_index = get_max_base_index ();
|
|
|
|
if (max_index < 0)
|
|
return;
|
|
unsigned res_len = max_index + 1;
|
|
new_indices->reserve_exact (res_len);
|
|
for (unsigned i = 0; i < res_len ; i++)
|
|
new_indices->quick_push (-1);
|
|
for (unsigned i = 0; i < adj_len ; i++)
|
|
{
|
|
ipa_adjusted_param *apm = &(*m_adj_params)[i];
|
|
if (apm->op == IPA_PARAM_OP_COPY)
|
|
(*new_indices)[apm->base_index] = i;
|
|
}
|
|
}
|
|
|
|
/* If a parameter with original INDEX has survived intact, return its new
|
|
index. Otherwise return -1. In that case, if it has been split and there
|
|
is a new parameter representing a portion at unit OFFSET for which a value
|
|
of a TYPE can be substituted, store its new index into SPLIT_INDEX,
|
|
otherwise store -1 there. */
|
|
int
|
|
ipa_param_adjustments::get_updated_index_or_split (int index,
|
|
unsigned unit_offset,
|
|
tree type, int *split_index)
|
|
{
|
|
unsigned adj_len = vec_safe_length (m_adj_params);
|
|
for (unsigned i = 0; i < adj_len ; i++)
|
|
{
|
|
ipa_adjusted_param *apm = &(*m_adj_params)[i];
|
|
if (apm->base_index != index)
|
|
continue;
|
|
if (apm->op == IPA_PARAM_OP_COPY)
|
|
return i;
|
|
if (apm->op == IPA_PARAM_OP_SPLIT
|
|
&& apm->unit_offset == unit_offset)
|
|
{
|
|
if (useless_type_conversion_p (apm->type, type))
|
|
*split_index = i;
|
|
else
|
|
*split_index = -1;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
*split_index = -1;
|
|
return -1;
|
|
}
|
|
|
|
/* Return the original index for the given new parameter index. Return a
|
|
negative number if not available. */
|
|
|
|
int
|
|
ipa_param_adjustments::get_original_index (int newidx)
|
|
{
|
|
const ipa_adjusted_param *adj = &(*m_adj_params)[newidx];
|
|
if (adj->op != IPA_PARAM_OP_COPY)
|
|
return -1;
|
|
return adj->base_index;
|
|
}
|
|
|
|
/* Return true if the first parameter (assuming there was one) survives the
|
|
transformation intact and remains the first one. */
|
|
|
|
bool
|
|
ipa_param_adjustments::first_param_intact_p ()
|
|
{
|
|
return (!vec_safe_is_empty (m_adj_params)
|
|
&& (*m_adj_params)[0].op == IPA_PARAM_OP_COPY
|
|
&& (*m_adj_params)[0].base_index == 0);
|
|
}
|
|
|
|
/* Return true if we have to change what has formerly been a method into a
|
|
function. */
|
|
|
|
bool
|
|
ipa_param_adjustments::method2func_p (tree orig_type)
|
|
{
|
|
return ((TREE_CODE (orig_type) == METHOD_TYPE) && !first_param_intact_p ());
|
|
}
|
|
|
|
/* Given function type OLD_TYPE, return a new type derived from it after
|
|
performing all atored modifications. TYPE_ORIGINAL_P should be true when
|
|
OLD_TYPE refers to the type before any IPA transformations, as opposed to a
|
|
type that can be an intermediate one in between various IPA
|
|
transformations. */
|
|
|
|
tree
|
|
ipa_param_adjustments::build_new_function_type (tree old_type,
|
|
bool type_original_p)
|
|
{
|
|
auto_vec<tree,16> new_param_types, *new_param_types_p;
|
|
if (prototype_p (old_type))
|
|
{
|
|
auto_vec<tree, 16> otypes;
|
|
push_function_arg_types (&otypes, old_type);
|
|
fill_vector_of_new_param_types (&new_param_types, &otypes, m_adj_params,
|
|
!type_original_p);
|
|
new_param_types_p = &new_param_types;
|
|
}
|
|
else
|
|
new_param_types_p = NULL;
|
|
|
|
/* Check if any params type cares about are modified. In this case will
|
|
need to drop some type attributes. */
|
|
bool modified = false;
|
|
size_t index = 0;
|
|
if (m_adj_params)
|
|
for (tree t = TYPE_ARG_TYPES (old_type);
|
|
t && (int)index < m_always_copy_start && !modified;
|
|
t = TREE_CHAIN (t), index++)
|
|
if (index >= m_adj_params->length ()
|
|
|| get_original_index (index) != (int)index)
|
|
modified = true;
|
|
|
|
|
|
return build_adjusted_function_type (old_type, new_param_types_p,
|
|
method2func_p (old_type), m_skip_return,
|
|
modified);
|
|
}
|
|
|
|
/* Build variant of function decl ORIG_DECL which has no return value if
|
|
M_SKIP_RETURN is true and, if ORIG_DECL's types or parameters is known, has
|
|
this type adjusted as indicated in M_ADJ_PARAMS. Arguments from
|
|
DECL_ARGUMENTS list are not processed now, since they are linked by
|
|
TREE_CHAIN directly and not accessible in LTO during WPA. The caller is
|
|
responsible for eliminating them when clones are properly materialized. */
|
|
|
|
tree
|
|
ipa_param_adjustments::adjust_decl (tree orig_decl)
|
|
{
|
|
tree new_decl = copy_node (orig_decl);
|
|
tree orig_type = TREE_TYPE (orig_decl);
|
|
if (prototype_p (orig_type)
|
|
|| (m_skip_return && !VOID_TYPE_P (TREE_TYPE (orig_type))))
|
|
{
|
|
tree new_type = build_new_function_type (orig_type, false);
|
|
TREE_TYPE (new_decl) = new_type;
|
|
}
|
|
if (method2func_p (orig_type))
|
|
DECL_VINDEX (new_decl) = NULL_TREE;
|
|
|
|
/* When signature changes, we need to clear builtin info. */
|
|
if (fndecl_built_in_p (new_decl))
|
|
set_decl_built_in_function (new_decl, NOT_BUILT_IN, 0);
|
|
|
|
DECL_VIRTUAL_P (new_decl) = 0;
|
|
DECL_LANG_SPECIFIC (new_decl) = NULL;
|
|
|
|
/* Drop MALLOC attribute for a void function. */
|
|
if (m_skip_return)
|
|
DECL_IS_MALLOC (new_decl) = 0;
|
|
|
|
return new_decl;
|
|
}
|
|
|
|
/* Wrapper around get_base_ref_and_offset for cases interesting for IPA-SRA
|
|
transformations. Return true if EXPR has an interesting form and fill in
|
|
*BASE_P and *UNIT_OFFSET_P with the appropriate info. */
|
|
|
|
static bool
|
|
isra_get_ref_base_and_offset (tree expr, tree *base_p, unsigned *unit_offset_p)
|
|
{
|
|
HOST_WIDE_INT offset, size;
|
|
bool reverse;
|
|
tree base
|
|
= get_ref_base_and_extent_hwi (expr, &offset, &size, &reverse);
|
|
if (!base || size < 0)
|
|
return false;
|
|
|
|
if ((offset % BITS_PER_UNIT) != 0)
|
|
return false;
|
|
|
|
if (TREE_CODE (base) == MEM_REF)
|
|
{
|
|
poly_int64 plmoff = mem_ref_offset (base).force_shwi ();
|
|
HOST_WIDE_INT moff;
|
|
bool is_cst = plmoff.is_constant (&moff);
|
|
if (!is_cst)
|
|
return false;
|
|
offset += moff * BITS_PER_UNIT;
|
|
base = TREE_OPERAND (base, 0);
|
|
}
|
|
|
|
if (offset < 0 || (offset / BITS_PER_UNIT) > UINT_MAX)
|
|
return false;
|
|
|
|
*base_p = base;
|
|
*unit_offset_p = offset / BITS_PER_UNIT;
|
|
return true;
|
|
}
|
|
|
|
/* Modify actual arguments of a function call in statement currently belonging
|
|
to CS, and make it call CS->callee->decl. Return the new statement that
|
|
replaced the old one. When invoked, cfun and current_function_decl have to
|
|
be set to the caller. */
|
|
|
|
gcall *
|
|
ipa_param_adjustments::modify_call (cgraph_edge *cs,
|
|
bool update_references)
|
|
{
|
|
gcall *stmt = cs->call_stmt;
|
|
tree callee_decl = cs->callee->decl;
|
|
|
|
ipa_edge_modification_info *mod_info
|
|
= ipa_edge_modifications ? ipa_edge_modifications->get (cs) : NULL;
|
|
if (mod_info && symtab->dump_file)
|
|
{
|
|
fprintf (symtab->dump_file, "Information about pre-exiting "
|
|
"modifications.\n Index map:");
|
|
unsigned idx_len = mod_info->index_map.length ();
|
|
for (unsigned i = 0; i < idx_len; i++)
|
|
fprintf (symtab->dump_file, " %i", mod_info->index_map[i]);
|
|
fprintf (symtab->dump_file, "\n Pass-through split map: ");
|
|
unsigned ptm_len = mod_info->pass_through_map.length ();
|
|
for (unsigned i = 0; i < ptm_len; i++)
|
|
fprintf (symtab->dump_file,
|
|
" (base_index: %u, offset: %u, new_index: %i)",
|
|
mod_info->pass_through_map[i].base_index,
|
|
mod_info->pass_through_map[i].unit_offset,
|
|
mod_info->pass_through_map[i].new_index);
|
|
fprintf (symtab->dump_file, "\n Always-copy delta: %i\n",
|
|
mod_info->always_copy_delta);
|
|
}
|
|
|
|
unsigned len = vec_safe_length (m_adj_params);
|
|
auto_vec<tree, 16> vargs (len);
|
|
unsigned old_nargs = gimple_call_num_args (stmt);
|
|
unsigned orig_nargs = mod_info ? mod_info->index_map.length () : old_nargs;
|
|
auto_vec<bool, 16> kept (old_nargs);
|
|
kept.quick_grow_cleared (old_nargs);
|
|
|
|
cgraph_node *current_node = cgraph_node::get (current_function_decl);
|
|
if (update_references)
|
|
current_node->remove_stmt_references (stmt);
|
|
|
|
gimple_stmt_iterator gsi = gsi_for_stmt (stmt);
|
|
gimple_stmt_iterator prev_gsi = gsi;
|
|
gsi_prev (&prev_gsi);
|
|
for (unsigned i = 0; i < len; i++)
|
|
{
|
|
ipa_adjusted_param *apm = &(*m_adj_params)[i];
|
|
if (apm->op == IPA_PARAM_OP_COPY)
|
|
{
|
|
int index = apm->base_index;
|
|
if ((unsigned) index >= orig_nargs)
|
|
/* Can happen if the original call has argument mismatch,
|
|
ignore. */
|
|
continue;
|
|
if (mod_info)
|
|
{
|
|
index = mod_info->index_map[apm->base_index];
|
|
gcc_assert (index >= 0);
|
|
}
|
|
|
|
tree arg = gimple_call_arg (stmt, index);
|
|
|
|
vargs.quick_push (arg);
|
|
kept[index] = true;
|
|
continue;
|
|
}
|
|
|
|
/* At the moment the only user of IPA_PARAM_OP_NEW modifies calls itself.
|
|
If we ever want to support it during WPA IPA stage, we'll need a
|
|
mechanism to call into the IPA passes that introduced them. Currently
|
|
we simply mandate that IPA infrastructure understands all argument
|
|
modifications. Remember, edge redirection/modification is done only
|
|
once, not in steps for each pass modifying the callee like clone
|
|
materialization. */
|
|
gcc_assert (apm->op == IPA_PARAM_OP_SPLIT);
|
|
|
|
/* We have to handle pass-through changes differently using the map
|
|
clone materialziation might have left behind. */
|
|
tree repl = NULL_TREE;
|
|
unsigned ptm_len = mod_info ? mod_info->pass_through_map.length () : 0;
|
|
for (unsigned j = 0; j < ptm_len; j++)
|
|
if (mod_info->pass_through_map[j].base_index == apm->base_index
|
|
&& mod_info->pass_through_map[j].unit_offset == apm->unit_offset)
|
|
{
|
|
int repl_idx = mod_info->pass_through_map[j].new_index;
|
|
gcc_assert (repl_idx >= 0);
|
|
repl = gimple_call_arg (stmt, repl_idx);
|
|
break;
|
|
}
|
|
if (repl)
|
|
{
|
|
vargs.quick_push (repl);
|
|
continue;
|
|
}
|
|
|
|
int index = apm->base_index;
|
|
if ((unsigned) index >= orig_nargs)
|
|
/* Can happen if the original call has argument mismatch, ignore. */
|
|
continue;
|
|
if (mod_info)
|
|
{
|
|
index = mod_info->index_map[apm->base_index];
|
|
gcc_assert (index >= 0);
|
|
}
|
|
tree base = gimple_call_arg (stmt, index);
|
|
|
|
/* We create a new parameter out of the value of the old one, we can
|
|
do the following kind of transformations:
|
|
|
|
- A scalar passed by reference, potentially as a part of a larger
|
|
aggregate, is converted to a scalar passed by value.
|
|
|
|
- A part of an aggregate is passed instead of the whole aggregate. */
|
|
|
|
location_t loc = gimple_location (stmt);
|
|
tree off;
|
|
bool deref_base = false;
|
|
unsigned int deref_align = 0;
|
|
if (TREE_CODE (base) != ADDR_EXPR
|
|
&& is_gimple_reg_type (TREE_TYPE (base)))
|
|
{
|
|
/* Detect type mismatches in calls in invalid programs and make a
|
|
poor attempt to gracefully convert them so that we don't ICE. */
|
|
if (!POINTER_TYPE_P (TREE_TYPE (base)))
|
|
base = force_value_to_type (ptr_type_node, base);
|
|
|
|
off = build_int_cst (apm->alias_ptr_type, apm->unit_offset);
|
|
}
|
|
else
|
|
{
|
|
bool addrof;
|
|
if (TREE_CODE (base) == ADDR_EXPR)
|
|
{
|
|
base = TREE_OPERAND (base, 0);
|
|
addrof = true;
|
|
}
|
|
else
|
|
addrof = false;
|
|
|
|
tree prev_base = base;
|
|
poly_int64 base_offset;
|
|
base = get_addr_base_and_unit_offset (base, &base_offset);
|
|
|
|
/* Aggregate arguments can have non-invariant addresses. */
|
|
if (!base)
|
|
{
|
|
base = build_fold_addr_expr (prev_base);
|
|
off = build_int_cst (apm->alias_ptr_type, apm->unit_offset);
|
|
}
|
|
else if (TREE_CODE (base) == MEM_REF)
|
|
{
|
|
if (!addrof)
|
|
{
|
|
deref_base = true;
|
|
deref_align = TYPE_ALIGN (TREE_TYPE (base));
|
|
}
|
|
off = build_int_cst (apm->alias_ptr_type,
|
|
base_offset + apm->unit_offset);
|
|
off = int_const_binop (PLUS_EXPR, TREE_OPERAND (base, 1),
|
|
off);
|
|
base = TREE_OPERAND (base, 0);
|
|
}
|
|
else
|
|
{
|
|
off = build_int_cst (apm->alias_ptr_type,
|
|
base_offset + apm->unit_offset);
|
|
base = build_fold_addr_expr (base);
|
|
}
|
|
}
|
|
|
|
tree type = apm->type;
|
|
unsigned int align;
|
|
unsigned HOST_WIDE_INT misalign;
|
|
|
|
if (deref_base)
|
|
{
|
|
align = deref_align;
|
|
misalign = 0;
|
|
}
|
|
else
|
|
{
|
|
get_pointer_alignment_1 (base, &align, &misalign);
|
|
/* All users must make sure that we can be optimistic when it
|
|
comes to alignment in this case (by inspecting the final users
|
|
of these new parameters). */
|
|
if (TYPE_ALIGN (type) > align)
|
|
align = TYPE_ALIGN (type);
|
|
}
|
|
misalign
|
|
+= (offset_int::from (wi::to_wide (off), SIGNED).to_short_addr ()
|
|
* BITS_PER_UNIT);
|
|
misalign = misalign & (align - 1);
|
|
if (misalign != 0)
|
|
align = least_bit_hwi (misalign);
|
|
if (align < TYPE_ALIGN (type))
|
|
type = build_aligned_type (type, align);
|
|
base = force_gimple_operand_gsi (&gsi, base,
|
|
true, NULL, true, GSI_SAME_STMT);
|
|
tree expr = fold_build2_loc (loc, MEM_REF, type, base, off);
|
|
REF_REVERSE_STORAGE_ORDER (expr) = apm->reverse;
|
|
/* If expr is not a valid gimple call argument emit
|
|
a load into a temporary. */
|
|
if (is_gimple_reg_type (TREE_TYPE (expr)))
|
|
{
|
|
gimple *tem = gimple_build_assign (NULL_TREE, expr);
|
|
if (gimple_in_ssa_p (cfun))
|
|
{
|
|
gimple_set_vuse (tem, gimple_vuse (stmt));
|
|
expr = make_ssa_name (TREE_TYPE (expr), tem);
|
|
}
|
|
else
|
|
expr = create_tmp_reg (TREE_TYPE (expr));
|
|
gimple_assign_set_lhs (tem, expr);
|
|
gsi_insert_before (&gsi, tem, GSI_SAME_STMT);
|
|
}
|
|
vargs.quick_push (expr);
|
|
}
|
|
|
|
if (m_always_copy_start >= 0)
|
|
{
|
|
int always_copy_start = m_always_copy_start;
|
|
if (mod_info)
|
|
{
|
|
always_copy_start += mod_info->always_copy_delta;
|
|
gcc_assert (always_copy_start >= 0);
|
|
}
|
|
for (unsigned i = always_copy_start; i < old_nargs; i++)
|
|
vargs.safe_push (gimple_call_arg (stmt, i));
|
|
}
|
|
|
|
/* For optimized away parameters, add on the caller side
|
|
before the call
|
|
DEBUG D#X => parm_Y(D)
|
|
stmts and associate D#X with parm in decl_debug_args_lookup
|
|
vector to say for debug info that if parameter parm had been passed,
|
|
it would have value parm_Y(D). */
|
|
tree old_decl = gimple_call_fndecl (stmt);
|
|
if (MAY_HAVE_DEBUG_BIND_STMTS && old_decl && callee_decl)
|
|
{
|
|
vec<tree, va_gc> **debug_args = NULL;
|
|
unsigned i = 0;
|
|
cgraph_node *callee_node = cgraph_node::get (callee_decl);
|
|
|
|
/* FIXME: we don't seem to be able to insert debug args before clone
|
|
is materialized. Materializing them early leads to extra memory
|
|
use. */
|
|
if (callee_node->clone_of)
|
|
callee_node->get_untransformed_body ();
|
|
for (tree old_parm = DECL_ARGUMENTS (old_decl);
|
|
old_parm && i < old_nargs && ((int) i) < m_always_copy_start;
|
|
old_parm = DECL_CHAIN (old_parm), i++)
|
|
{
|
|
if (!is_gimple_reg (old_parm) || kept[i])
|
|
continue;
|
|
tree arg;
|
|
if (mod_info)
|
|
{
|
|
if (mod_info->index_map[i] < 0)
|
|
continue;
|
|
arg = gimple_call_arg (stmt, mod_info->index_map[i]);
|
|
}
|
|
else
|
|
arg = gimple_call_arg (stmt, i);
|
|
|
|
tree origin = DECL_ORIGIN (old_parm);
|
|
if (!useless_type_conversion_p (TREE_TYPE (origin), TREE_TYPE (arg)))
|
|
{
|
|
if (!fold_convertible_p (TREE_TYPE (origin), arg))
|
|
continue;
|
|
tree rhs1;
|
|
if (TREE_CODE (arg) == SSA_NAME
|
|
&& gimple_assign_cast_p (SSA_NAME_DEF_STMT (arg))
|
|
&& (rhs1
|
|
= gimple_assign_rhs1 (SSA_NAME_DEF_STMT (arg)))
|
|
&& useless_type_conversion_p (TREE_TYPE (origin),
|
|
TREE_TYPE (rhs1)))
|
|
arg = rhs1;
|
|
else
|
|
arg = fold_convert_loc (gimple_location (stmt),
|
|
TREE_TYPE (origin), arg);
|
|
}
|
|
if (debug_args == NULL)
|
|
debug_args = decl_debug_args_insert (callee_decl);
|
|
unsigned int ix;
|
|
tree ddecl = NULL_TREE;
|
|
for (ix = 0; vec_safe_iterate (*debug_args, ix, &ddecl); ix += 2)
|
|
if (ddecl == origin)
|
|
{
|
|
ddecl = (**debug_args)[ix + 1];
|
|
break;
|
|
}
|
|
if (ddecl == NULL)
|
|
{
|
|
ddecl = build_debug_expr_decl (TREE_TYPE (origin));
|
|
/* FIXME: Is setting the mode really necessary? */
|
|
SET_DECL_MODE (ddecl, DECL_MODE (origin));
|
|
|
|
vec_safe_push (*debug_args, origin);
|
|
vec_safe_push (*debug_args, ddecl);
|
|
}
|
|
gimple *def_temp = gimple_build_debug_bind (ddecl,
|
|
unshare_expr (arg), stmt);
|
|
gsi_insert_before (&gsi, def_temp, GSI_SAME_STMT);
|
|
}
|
|
}
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "replacing stmt:");
|
|
print_gimple_stmt (dump_file, gsi_stmt (gsi), 0);
|
|
}
|
|
|
|
gcall *new_stmt = gimple_build_call_vec (callee_decl, vargs);
|
|
|
|
tree ssa_to_remove = NULL;
|
|
if (tree lhs = gimple_call_lhs (stmt))
|
|
{
|
|
if (!m_skip_return)
|
|
gimple_call_set_lhs (new_stmt, lhs);
|
|
else if (TREE_CODE (lhs) == SSA_NAME)
|
|
{
|
|
/* LHS should now by a default-def SSA. Unfortunately default-def
|
|
SSA_NAMEs need a backing variable (or at least some code examining
|
|
SSAs assumes it is non-NULL). So we either have to re-use the
|
|
decl we have at hand or introdice a new one. */
|
|
tree repl = create_tmp_var (TREE_TYPE (lhs), "removed_return");
|
|
repl = get_or_create_ssa_default_def (cfun, repl);
|
|
SSA_NAME_IS_DEFAULT_DEF (repl) = true;
|
|
imm_use_iterator ui;
|
|
use_operand_p use_p;
|
|
gimple *using_stmt;
|
|
FOR_EACH_IMM_USE_STMT (using_stmt, ui, lhs)
|
|
{
|
|
FOR_EACH_IMM_USE_ON_STMT (use_p, ui)
|
|
{
|
|
SET_USE (use_p, repl);
|
|
}
|
|
update_stmt (using_stmt);
|
|
}
|
|
ssa_to_remove = lhs;
|
|
}
|
|
}
|
|
|
|
gimple_set_block (new_stmt, gimple_block (stmt));
|
|
if (gimple_has_location (stmt))
|
|
gimple_set_location (new_stmt, gimple_location (stmt));
|
|
gimple_call_set_chain (new_stmt, gimple_call_chain (stmt));
|
|
gimple_call_copy_flags (new_stmt, stmt);
|
|
if (gimple_in_ssa_p (cfun))
|
|
gimple_move_vops (new_stmt, stmt);
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "with stmt:");
|
|
print_gimple_stmt (dump_file, new_stmt, 0);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
gsi_replace (&gsi, new_stmt, true);
|
|
if (ssa_to_remove)
|
|
release_ssa_name (ssa_to_remove);
|
|
if (update_references)
|
|
do
|
|
{
|
|
current_node->record_stmt_references (gsi_stmt (gsi));
|
|
gsi_prev (&gsi);
|
|
}
|
|
while (gsi_stmt (gsi) != gsi_stmt (prev_gsi));
|
|
|
|
if (mod_info)
|
|
ipa_edge_modifications->remove (cs);
|
|
return new_stmt;
|
|
}
|
|
|
|
/* Dump information contained in the object in textual form to F. */
|
|
|
|
void
|
|
ipa_param_adjustments::dump (FILE *f)
|
|
{
|
|
fprintf (f, " m_always_copy_start: %i\n", m_always_copy_start);
|
|
ipa_dump_adjusted_parameters (f, m_adj_params);
|
|
if (m_skip_return)
|
|
fprintf (f, " Will SKIP return.\n");
|
|
}
|
|
|
|
/* Dump information contained in the object in textual form to stderr. */
|
|
|
|
void
|
|
ipa_param_adjustments::debug ()
|
|
{
|
|
dump (stderr);
|
|
}
|
|
|
|
/* Register that REPLACEMENT should replace parameter described in APM. */
|
|
|
|
void
|
|
ipa_param_body_adjustments::register_replacement (ipa_adjusted_param *apm,
|
|
tree replacement)
|
|
{
|
|
gcc_checking_assert (apm->op == IPA_PARAM_OP_SPLIT
|
|
|| apm->op == IPA_PARAM_OP_NEW);
|
|
gcc_checking_assert (!apm->prev_clone_adjustment);
|
|
ipa_param_body_replacement psr;
|
|
psr.base = m_oparms[apm->prev_clone_index];
|
|
psr.repl = replacement;
|
|
psr.dummy = NULL_TREE;
|
|
psr.unit_offset = apm->unit_offset;
|
|
m_replacements.safe_push (psr);
|
|
}
|
|
|
|
/* Copy or not, as appropriate given m_id and decl context, a pre-existing
|
|
PARM_DECL T so that it can be included in the parameters of the modified
|
|
function. */
|
|
|
|
tree
|
|
ipa_param_body_adjustments::carry_over_param (tree t)
|
|
{
|
|
tree new_parm;
|
|
if (m_id)
|
|
{
|
|
new_parm = remap_decl (t, m_id);
|
|
if (TREE_CODE (new_parm) != PARM_DECL)
|
|
new_parm = m_id->copy_decl (t, m_id);
|
|
}
|
|
else if (DECL_CONTEXT (t) != m_fndecl)
|
|
{
|
|
new_parm = copy_node (t);
|
|
DECL_CONTEXT (new_parm) = m_fndecl;
|
|
}
|
|
else
|
|
new_parm = t;
|
|
return new_parm;
|
|
}
|
|
|
|
/* Populate m_dead_stmts given that DEAD_PARAM is going to be removed without
|
|
any replacement or splitting. REPL is the replacement VAR_SECL to base any
|
|
remaining uses of a removed parameter on. Push all removed SSA names that
|
|
are used within debug statements to DEBUGSTACK. */
|
|
|
|
void
|
|
ipa_param_body_adjustments::mark_dead_statements (tree dead_param,
|
|
vec<tree> *debugstack)
|
|
{
|
|
/* Current IPA analyses which remove unused parameters never remove a
|
|
non-gimple register ones which have any use except as parameters in other
|
|
calls, so we can safely leve them as they are. */
|
|
if (!is_gimple_reg (dead_param))
|
|
return;
|
|
tree parm_ddef = ssa_default_def (m_id->src_cfun, dead_param);
|
|
if (!parm_ddef || has_zero_uses (parm_ddef))
|
|
return;
|
|
|
|
auto_vec<tree, 4> stack;
|
|
hash_set<tree> used_in_debug;
|
|
m_dead_ssas.add (parm_ddef);
|
|
stack.safe_push (parm_ddef);
|
|
while (!stack.is_empty ())
|
|
{
|
|
imm_use_iterator imm_iter;
|
|
use_operand_p use_p;
|
|
tree t = stack.pop ();
|
|
|
|
insert_decl_map (m_id, t, error_mark_node);
|
|
FOR_EACH_IMM_USE_FAST (use_p, imm_iter, t)
|
|
{
|
|
gimple *stmt = USE_STMT (use_p);
|
|
|
|
/* Calls containing dead arguments cannot be deleted,
|
|
modify_call_stmt will instead remove just the argument later on.
|
|
If isra_track_scalar_value_uses in ipa-sra.cc is extended to look
|
|
through const functions, we will need to do so here too. */
|
|
if (is_gimple_call (stmt)
|
|
|| (m_id->blocks_to_copy
|
|
&& !bitmap_bit_p (m_id->blocks_to_copy,
|
|
gimple_bb (stmt)->index)))
|
|
continue;
|
|
|
|
if (is_gimple_debug (stmt))
|
|
{
|
|
m_dead_stmts.add (stmt);
|
|
gcc_assert (gimple_debug_bind_p (stmt));
|
|
if (!used_in_debug.contains (t))
|
|
{
|
|
used_in_debug.add (t);
|
|
debugstack->safe_push (t);
|
|
}
|
|
}
|
|
else if (gimple_code (stmt) == GIMPLE_PHI)
|
|
{
|
|
gphi *phi = as_a <gphi *> (stmt);
|
|
int ix = PHI_ARG_INDEX_FROM_USE (use_p);
|
|
|
|
if (!m_id->blocks_to_copy
|
|
|| bitmap_bit_p (m_id->blocks_to_copy,
|
|
gimple_phi_arg_edge (phi, ix)->src->index))
|
|
{
|
|
m_dead_stmts.add (phi);
|
|
tree res = gimple_phi_result (phi);
|
|
if (!m_dead_ssas.add (res))
|
|
stack.safe_push (res);
|
|
}
|
|
}
|
|
else if (is_gimple_assign (stmt))
|
|
{
|
|
m_dead_stmts.add (stmt);
|
|
if (!gimple_clobber_p (stmt))
|
|
{
|
|
tree lhs = gimple_assign_lhs (stmt);
|
|
gcc_assert (TREE_CODE (lhs) == SSA_NAME);
|
|
if (!m_dead_ssas.add (lhs))
|
|
stack.safe_push (lhs);
|
|
}
|
|
}
|
|
else
|
|
/* IPA-SRA does not analyze other types of statements. */
|
|
gcc_unreachable ();
|
|
}
|
|
}
|
|
|
|
if (!MAY_HAVE_DEBUG_STMTS)
|
|
{
|
|
gcc_assert (debugstack->is_empty ());
|
|
return;
|
|
}
|
|
|
|
tree dp_ddecl = build_debug_expr_decl (TREE_TYPE (dead_param));
|
|
/* FIXME: Is setting the mode really necessary? */
|
|
SET_DECL_MODE (dp_ddecl, DECL_MODE (dead_param));
|
|
m_dead_ssa_debug_equiv.put (parm_ddef, dp_ddecl);
|
|
}
|
|
|
|
/* Callback to walk_tree. If REMAP is an SSA_NAME that is present in hash_map
|
|
passed in DATA, replace it with unshared version of what it was mapped to.
|
|
If an SSA argument would be remapped to NULL, the whole operation needs to
|
|
abort which is signaled by returning error_mark_node. */
|
|
|
|
static tree
|
|
replace_with_mapped_expr (tree *remap, int *walk_subtrees, void *data)
|
|
{
|
|
if (TYPE_P (*remap))
|
|
{
|
|
*walk_subtrees = 0;
|
|
return 0;
|
|
}
|
|
if (TREE_CODE (*remap) != SSA_NAME)
|
|
return 0;
|
|
|
|
*walk_subtrees = 0;
|
|
|
|
hash_map<tree, tree> *equivs = (hash_map<tree, tree> *) data;
|
|
if (tree *p = equivs->get (*remap))
|
|
{
|
|
if (!*p)
|
|
return error_mark_node;
|
|
*remap = unshare_expr (*p);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Replace all occurances of SSAs in m_dead_ssa_debug_equiv in t with what they
|
|
are mapped to. */
|
|
|
|
void
|
|
ipa_param_body_adjustments::remap_with_debug_expressions (tree *t)
|
|
{
|
|
/* If *t is an SSA_NAME which should have its debug statements reset, it is
|
|
mapped to NULL in the hash_map.
|
|
|
|
It is perhaps simpler to handle the SSA_NAME cases directly and only
|
|
invoke walk_tree on more complex expressions. When
|
|
remap_with_debug_expressions is called from tree-inline.cc, a to-be-reset
|
|
SSA_NAME can be an operand to such expressions and the entire debug
|
|
variable we are remapping should be reset. This is signaled by walk_tree
|
|
returning error_mark_node and done by setting *t to NULL. */
|
|
if (TREE_CODE (*t) == SSA_NAME)
|
|
{
|
|
if (tree *p = m_dead_ssa_debug_equiv.get (*t))
|
|
*t = *p;
|
|
}
|
|
else if (walk_tree (t, replace_with_mapped_expr,
|
|
&m_dead_ssa_debug_equiv, NULL) == error_mark_node)
|
|
*t = NULL_TREE;
|
|
}
|
|
|
|
/* For an SSA_NAME DEAD_SSA which is about to be DCEd because it is based on a
|
|
useless parameter, prepare an expression that should represent it in
|
|
debug_binds in the cloned function and add a mapping from DEAD_SSA to
|
|
m_dead_ssa_debug_equiv. That mapping is to NULL when the associated
|
|
debug_statement has to be reset instead. In such case return false,
|
|
ottherwise return true. If DEAD_SSA comes from a basic block which is not
|
|
about to be copied, ignore it and return true. */
|
|
|
|
bool
|
|
ipa_param_body_adjustments::prepare_debug_expressions (tree dead_ssa)
|
|
{
|
|
gcc_checking_assert (m_dead_ssas.contains (dead_ssa));
|
|
if (tree *d = m_dead_ssa_debug_equiv.get (dead_ssa))
|
|
return (*d != NULL_TREE);
|
|
|
|
gcc_assert (!SSA_NAME_IS_DEFAULT_DEF (dead_ssa));
|
|
gimple *def = SSA_NAME_DEF_STMT (dead_ssa);
|
|
if (m_id->blocks_to_copy
|
|
&& !bitmap_bit_p (m_id->blocks_to_copy, gimple_bb (def)->index))
|
|
return true;
|
|
|
|
if (gimple_code (def) == GIMPLE_PHI)
|
|
{
|
|
/* In theory, we could ignore all SSAs coming from BBs not in
|
|
m_id->blocks_to_copy but at the time of the writing this code that
|
|
should never really be the case because only fnsplit uses that bitmap,
|
|
so don't bother. */
|
|
tree value = degenerate_phi_result (as_a <gphi *> (def));
|
|
if (!value
|
|
|| (m_dead_ssas.contains (value)
|
|
&& !prepare_debug_expressions (value)))
|
|
{
|
|
m_dead_ssa_debug_equiv.put (dead_ssa, NULL_TREE);
|
|
return false;
|
|
}
|
|
|
|
gcc_assert (TREE_CODE (value) == SSA_NAME);
|
|
tree *d = m_dead_ssa_debug_equiv.get (value);
|
|
m_dead_ssa_debug_equiv.put (dead_ssa, *d);
|
|
return true;
|
|
}
|
|
|
|
bool lost = false;
|
|
use_operand_p use_p;
|
|
ssa_op_iter oi;
|
|
FOR_EACH_PHI_OR_STMT_USE (use_p, def, oi, SSA_OP_USE)
|
|
{
|
|
tree use = USE_FROM_PTR (use_p);
|
|
if (m_dead_ssas.contains (use)
|
|
&& !prepare_debug_expressions (use))
|
|
{
|
|
lost = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lost)
|
|
{
|
|
m_dead_ssa_debug_equiv.put (dead_ssa, NULL_TREE);
|
|
return false;
|
|
}
|
|
|
|
if (is_gimple_assign (def))
|
|
{
|
|
gcc_assert (!gimple_clobber_p (def));
|
|
if (gimple_assign_copy_p (def)
|
|
&& TREE_CODE (gimple_assign_rhs1 (def)) == SSA_NAME)
|
|
{
|
|
tree d = *m_dead_ssa_debug_equiv.get (gimple_assign_rhs1 (def));
|
|
gcc_assert (d);
|
|
m_dead_ssa_debug_equiv.put (dead_ssa, d);
|
|
return true;
|
|
}
|
|
|
|
tree val
|
|
= unshare_expr_without_location (gimple_assign_rhs_to_tree (def));
|
|
remap_with_debug_expressions (&val);
|
|
|
|
tree vexpr = build_debug_expr_decl (TREE_TYPE (val));
|
|
m_dead_stmt_debug_equiv.put (def, val);
|
|
m_dead_ssa_debug_equiv.put (dead_ssa, vexpr);
|
|
return true;
|
|
}
|
|
else
|
|
gcc_unreachable ();
|
|
}
|
|
|
|
/* Common initialization performed by all ipa_param_body_adjustments
|
|
constructors. OLD_FNDECL is the declaration we take original arguments
|
|
from, (it may be the same as M_FNDECL). VARS, if non-NULL, is a pointer to
|
|
a chained list of new local variables. TREE_MAP is the IPA-CP produced
|
|
mapping of trees to constants.
|
|
|
|
The function is rather long but it really onlu initializes all data members
|
|
of the class. It creates new param DECLs, finds their new types, */
|
|
|
|
void
|
|
ipa_param_body_adjustments::common_initialization (tree old_fndecl,
|
|
tree *vars,
|
|
vec<ipa_replace_map *,
|
|
va_gc> *tree_map)
|
|
{
|
|
push_function_arg_decls (&m_oparms, old_fndecl);
|
|
auto_vec<tree,16> otypes;
|
|
if (TYPE_ARG_TYPES (TREE_TYPE (old_fndecl)) != NULL_TREE)
|
|
push_function_arg_types (&otypes, TREE_TYPE (old_fndecl));
|
|
else
|
|
{
|
|
auto_vec<tree,16> oparms;
|
|
push_function_arg_decls (&oparms, old_fndecl);
|
|
unsigned ocount = oparms.length ();
|
|
otypes.reserve_exact (ocount);
|
|
for (unsigned i = 0; i < ocount; i++)
|
|
otypes.quick_push (TREE_TYPE (oparms[i]));
|
|
}
|
|
fill_vector_of_new_param_types (&m_new_types, &otypes, m_adj_params, true);
|
|
|
|
auto_vec<bool, 16> kept;
|
|
kept.reserve_exact (m_oparms.length ());
|
|
kept.quick_grow_cleared (m_oparms.length ());
|
|
auto_vec<bool, 16> split;
|
|
split.reserve_exact (m_oparms.length ());
|
|
split.quick_grow_cleared (m_oparms.length ());
|
|
|
|
unsigned adj_len = vec_safe_length (m_adj_params);
|
|
m_method2func = ((TREE_CODE (TREE_TYPE (m_fndecl)) == METHOD_TYPE)
|
|
&& (adj_len == 0
|
|
|| (*m_adj_params)[0].op != IPA_PARAM_OP_COPY
|
|
|| (*m_adj_params)[0].base_index != 0));
|
|
|
|
/* The main job of the this function is to go over the vector of adjusted
|
|
parameters and create declarations or find corresponding old ones and push
|
|
them to m_new_decls. For IPA-SRA replacements it also creates
|
|
corresponding m_id->dst_node->clone.performed_splits entries. */
|
|
|
|
m_new_decls.reserve_exact (adj_len);
|
|
for (unsigned i = 0; i < adj_len ; i++)
|
|
{
|
|
ipa_adjusted_param *apm = &(*m_adj_params)[i];
|
|
unsigned prev_index = apm->prev_clone_index;
|
|
tree new_parm;
|
|
if (apm->op == IPA_PARAM_OP_COPY
|
|
|| apm->prev_clone_adjustment)
|
|
{
|
|
kept[prev_index] = true;
|
|
new_parm = carry_over_param (m_oparms[prev_index]);
|
|
m_new_decls.quick_push (new_parm);
|
|
}
|
|
else if (apm->op == IPA_PARAM_OP_NEW
|
|
|| apm->op == IPA_PARAM_OP_SPLIT)
|
|
{
|
|
tree new_type = m_new_types[i];
|
|
gcc_checking_assert (new_type);
|
|
new_parm = build_decl (UNKNOWN_LOCATION, PARM_DECL, NULL_TREE,
|
|
new_type);
|
|
const char *prefix = ipa_param_prefixes[apm->param_prefix_index];
|
|
DECL_NAME (new_parm) = create_tmp_var_name (prefix);
|
|
DECL_ARTIFICIAL (new_parm) = 1;
|
|
DECL_ARG_TYPE (new_parm) = new_type;
|
|
DECL_CONTEXT (new_parm) = m_fndecl;
|
|
TREE_USED (new_parm) = 1;
|
|
DECL_IGNORED_P (new_parm) = 1;
|
|
layout_decl (new_parm, 0);
|
|
m_new_decls.quick_push (new_parm);
|
|
|
|
if (apm->op == IPA_PARAM_OP_SPLIT)
|
|
{
|
|
m_split_modifications_p = true;
|
|
split[prev_index] = true;
|
|
register_replacement (apm, new_parm);
|
|
}
|
|
}
|
|
else
|
|
gcc_unreachable ();
|
|
}
|
|
|
|
if (tree_map)
|
|
{
|
|
/* Do not treat parameters which were replaced with a constant as
|
|
completely vanished. */
|
|
auto_vec <int, 16> index_mapping;
|
|
bool need_remap = false;
|
|
|
|
if (m_id)
|
|
{
|
|
clone_info *cinfo = clone_info::get (m_id->src_node);
|
|
if (cinfo && cinfo->param_adjustments)
|
|
{
|
|
cinfo->param_adjustments->get_updated_indices (&index_mapping);
|
|
need_remap = true;
|
|
}
|
|
}
|
|
|
|
for (unsigned i = 0; i < tree_map->length (); i++)
|
|
{
|
|
int parm_num = (*tree_map)[i]->parm_num;
|
|
gcc_assert (parm_num >= 0);
|
|
if (need_remap)
|
|
parm_num = index_mapping[parm_num];
|
|
kept[parm_num] = true;
|
|
}
|
|
}
|
|
|
|
/* As part of body modifications, we will also have to replace remaining uses
|
|
of remaining uses of removed PARM_DECLs (which do not however use the
|
|
initial value) with their VAR_DECL copies.
|
|
|
|
We do this differently with and without m_id. With m_id, we rely on its
|
|
mapping and create a replacement straight away. Without it, we have our
|
|
own mechanism for which we have to populate m_removed_decls vector. Just
|
|
don't mix them, that is why you should not call
|
|
replace_removed_params_ssa_names or perform_cfun_body_modifications when
|
|
you construct with ID not equal to NULL. */
|
|
|
|
auto_vec<tree, 8> ssas_to_process_debug;
|
|
unsigned op_len = m_oparms.length ();
|
|
for (unsigned i = 0; i < op_len; i++)
|
|
if (!kept[i])
|
|
{
|
|
if (m_id)
|
|
{
|
|
gcc_assert (!m_id->decl_map->get (m_oparms[i]));
|
|
tree var = copy_decl_to_var (m_oparms[i], m_id);
|
|
insert_decl_map (m_id, m_oparms[i], var);
|
|
/* Declare this new variable. */
|
|
DECL_CHAIN (var) = *vars;
|
|
*vars = var;
|
|
|
|
/* If this is not a split but a real removal, init hash sets
|
|
that will guide what not to copy to the new body. */
|
|
if (!split[i])
|
|
mark_dead_statements (m_oparms[i], &ssas_to_process_debug);
|
|
if (MAY_HAVE_DEBUG_STMTS
|
|
&& is_gimple_reg (m_oparms[i]))
|
|
m_reset_debug_decls.safe_push (m_oparms[i]);
|
|
}
|
|
else
|
|
{
|
|
m_removed_decls.safe_push (m_oparms[i]);
|
|
m_removed_map.put (m_oparms[i], m_removed_decls.length () - 1);
|
|
if (MAY_HAVE_DEBUG_STMTS
|
|
&& !kept[i]
|
|
&& is_gimple_reg (m_oparms[i]))
|
|
m_reset_debug_decls.safe_push (m_oparms[i]);
|
|
}
|
|
}
|
|
|
|
while (!ssas_to_process_debug.is_empty ())
|
|
prepare_debug_expressions (ssas_to_process_debug.pop ());
|
|
}
|
|
|
|
/* Constructor of ipa_param_body_adjustments from a simple list of
|
|
modifications to parameters listed in ADJ_PARAMS which will prepare ground
|
|
for modification of parameters of fndecl. Return value of the function will
|
|
not be removed and the object will assume it does not run as a part of
|
|
tree-function_versioning. */
|
|
|
|
ipa_param_body_adjustments
|
|
::ipa_param_body_adjustments (vec<ipa_adjusted_param, va_gc> *adj_params,
|
|
tree fndecl)
|
|
: m_adj_params (adj_params), m_adjustments (NULL), m_reset_debug_decls (),
|
|
m_split_modifications_p (false), m_dead_stmts (), m_dead_ssas (),
|
|
m_dead_ssa_debug_equiv (), m_dead_stmt_debug_equiv (), m_fndecl (fndecl),
|
|
m_id (NULL), m_oparms (), m_new_decls (), m_new_types (), m_replacements (),
|
|
m_removed_decls (), m_removed_map (), m_method2func (false)
|
|
{
|
|
common_initialization (fndecl, NULL, NULL);
|
|
}
|
|
|
|
/* Constructor of ipa_param_body_adjustments from ipa_param_adjustments in
|
|
ADJUSTMENTS which will prepare ground for modification of parameters of
|
|
fndecl. The object will assume it does not run as a part of
|
|
tree-function_versioning. */
|
|
|
|
ipa_param_body_adjustments
|
|
::ipa_param_body_adjustments (ipa_param_adjustments *adjustments,
|
|
tree fndecl)
|
|
: m_adj_params (adjustments->m_adj_params), m_adjustments (adjustments),
|
|
m_reset_debug_decls (), m_split_modifications_p (false), m_dead_stmts (),
|
|
m_dead_ssas (), m_dead_ssa_debug_equiv (), m_dead_stmt_debug_equiv (),
|
|
m_fndecl (fndecl), m_id (NULL), m_oparms (), m_new_decls (),
|
|
m_new_types (), m_replacements (), m_removed_decls (), m_removed_map (),
|
|
m_method2func (false)
|
|
{
|
|
common_initialization (fndecl, NULL, NULL);
|
|
}
|
|
|
|
/* Constructor of ipa_param_body_adjustments which sets it up as a part of
|
|
running tree_function_versioning. Planned modifications to the function are
|
|
in ADJUSTMENTS. FNDECL designates the new function clone which is being
|
|
modified. OLD_FNDECL is the function of which FNDECL is a clone (and which
|
|
at the time of invocation still share DECL_ARGUMENTS). ID is the
|
|
copy_body_data structure driving the wholy body copying process. VARS is a
|
|
pointer to the head of the list of new local variables, TREE_MAP is the map
|
|
that drives tree substitution in the cloning process. */
|
|
|
|
ipa_param_body_adjustments
|
|
::ipa_param_body_adjustments (ipa_param_adjustments *adjustments,
|
|
tree fndecl, tree old_fndecl,
|
|
copy_body_data *id, tree *vars,
|
|
vec<ipa_replace_map *, va_gc> *tree_map)
|
|
: m_adj_params (adjustments->m_adj_params), m_adjustments (adjustments),
|
|
m_reset_debug_decls (), m_split_modifications_p (false), m_dead_stmts (),
|
|
m_dead_ssas (), m_dead_ssa_debug_equiv (), m_dead_stmt_debug_equiv (),
|
|
m_fndecl (fndecl), m_id (id), m_oparms (), m_new_decls (), m_new_types (),
|
|
m_replacements (), m_removed_decls (), m_removed_map (),
|
|
m_method2func (false)
|
|
{
|
|
common_initialization (old_fndecl, vars, tree_map);
|
|
}
|
|
|
|
/* Chain new param decls up and return them. */
|
|
|
|
tree
|
|
ipa_param_body_adjustments::get_new_param_chain ()
|
|
{
|
|
tree result;
|
|
tree *link = &result;
|
|
|
|
unsigned len = vec_safe_length (m_adj_params);
|
|
for (unsigned i = 0; i < len; i++)
|
|
{
|
|
tree new_decl = m_new_decls[i];
|
|
*link = new_decl;
|
|
link = &DECL_CHAIN (new_decl);
|
|
}
|
|
*link = NULL_TREE;
|
|
return result;
|
|
}
|
|
|
|
/* Modify the function parameters FNDECL and its type according to the plan in
|
|
ADJUSTMENTS. This function needs to be called when the decl has not already
|
|
been processed with ipa_param_adjustments::adjust_decl, otherwise just
|
|
seting DECL_ARGUMENTS to whatever get_new_param_chain will do is enough. */
|
|
|
|
void
|
|
ipa_param_body_adjustments::modify_formal_parameters ()
|
|
{
|
|
tree orig_type = TREE_TYPE (m_fndecl);
|
|
DECL_ARGUMENTS (m_fndecl) = get_new_param_chain ();
|
|
|
|
/* When signature changes, we need to clear builtin info. */
|
|
if (fndecl_built_in_p (m_fndecl))
|
|
set_decl_built_in_function (m_fndecl, NOT_BUILT_IN, 0);
|
|
|
|
bool modified = false;
|
|
size_t index = 0;
|
|
if (m_adj_params)
|
|
for (tree t = TYPE_ARG_TYPES (orig_type);
|
|
t && !modified;
|
|
t = TREE_CHAIN (t), index++)
|
|
if (index >= m_adj_params->length ()
|
|
|| (*m_adj_params)[index].op != IPA_PARAM_OP_COPY
|
|
|| (*m_adj_params)[index].base_index != index)
|
|
modified = true;
|
|
|
|
/* At this point, removing return value is only implemented when going
|
|
through tree_function_versioning, not when modifying function body
|
|
directly. */
|
|
gcc_assert (!m_adjustments || !m_adjustments->m_skip_return);
|
|
tree new_type = build_adjusted_function_type (orig_type, &m_new_types,
|
|
m_method2func, false, modified);
|
|
|
|
TREE_TYPE (m_fndecl) = new_type;
|
|
DECL_VIRTUAL_P (m_fndecl) = 0;
|
|
DECL_LANG_SPECIFIC (m_fndecl) = NULL;
|
|
if (m_method2func)
|
|
DECL_VINDEX (m_fndecl) = NULL_TREE;
|
|
}
|
|
|
|
/* Given BASE and UNIT_OFFSET, find the corresponding record among replacement
|
|
structures. */
|
|
|
|
ipa_param_body_replacement *
|
|
ipa_param_body_adjustments::lookup_replacement_1 (tree base,
|
|
unsigned unit_offset)
|
|
{
|
|
unsigned int len = m_replacements.length ();
|
|
for (unsigned i = 0; i < len; i++)
|
|
{
|
|
ipa_param_body_replacement *pbr = &m_replacements[i];
|
|
|
|
if (pbr->base == base
|
|
&& (pbr->unit_offset == unit_offset))
|
|
return pbr;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Given BASE and UNIT_OFFSET, find the corresponding replacement expression
|
|
and return it, assuming it is known it does not hold value by reference or
|
|
in reverse storage order. */
|
|
|
|
tree
|
|
ipa_param_body_adjustments::lookup_replacement (tree base, unsigned unit_offset)
|
|
{
|
|
ipa_param_body_replacement *pbr = lookup_replacement_1 (base, unit_offset);
|
|
if (!pbr)
|
|
return NULL;
|
|
return pbr->repl;
|
|
}
|
|
|
|
/* If T is an SSA_NAME, return NULL if it is not a default def or
|
|
return its base variable if it is. If IGNORE_DEFAULT_DEF is true,
|
|
the base variable is always returned, regardless if it is a default
|
|
def. Return T if it is not an SSA_NAME. */
|
|
|
|
static tree
|
|
get_ssa_base_param (tree t, bool ignore_default_def)
|
|
{
|
|
if (TREE_CODE (t) == SSA_NAME)
|
|
{
|
|
if (ignore_default_def || SSA_NAME_IS_DEFAULT_DEF (t))
|
|
return SSA_NAME_VAR (t);
|
|
else
|
|
return NULL_TREE;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
/* Given an expression, return the structure describing how it should be
|
|
replaced if it accesses a part of a split parameter or NULL otherwise.
|
|
|
|
Do not free the result, it will be deallocated when the object is destroyed.
|
|
|
|
If IGNORE_DEFAULT_DEF is cleared, consider only SSA_NAMEs of PARM_DECLs
|
|
which are default definitions, if set, consider all SSA_NAMEs of
|
|
PARM_DECLs. */
|
|
|
|
ipa_param_body_replacement *
|
|
ipa_param_body_adjustments::get_expr_replacement (tree expr,
|
|
bool ignore_default_def)
|
|
{
|
|
tree base;
|
|
unsigned unit_offset;
|
|
|
|
if (!isra_get_ref_base_and_offset (expr, &base, &unit_offset))
|
|
return NULL;
|
|
|
|
base = get_ssa_base_param (base, ignore_default_def);
|
|
if (!base || TREE_CODE (base) != PARM_DECL)
|
|
return NULL;
|
|
return lookup_replacement_1 (base, unit_offset);
|
|
}
|
|
|
|
/* Given OLD_DECL, which is a PARM_DECL of a parameter that is being removed
|
|
(which includes it being split or replaced), return a new variable that
|
|
should be used for any SSA names that will remain in the function that
|
|
previously belonged to OLD_DECL. */
|
|
|
|
tree
|
|
ipa_param_body_adjustments::get_replacement_ssa_base (tree old_decl)
|
|
{
|
|
unsigned *idx = m_removed_map.get (old_decl);
|
|
if (!idx)
|
|
return NULL;
|
|
|
|
tree repl;
|
|
if (TREE_CODE (m_removed_decls[*idx]) == PARM_DECL)
|
|
{
|
|
gcc_assert (m_removed_decls[*idx] == old_decl);
|
|
repl = copy_var_decl (old_decl, DECL_NAME (old_decl),
|
|
TREE_TYPE (old_decl));
|
|
m_removed_decls[*idx] = repl;
|
|
}
|
|
else
|
|
repl = m_removed_decls[*idx];
|
|
return repl;
|
|
}
|
|
|
|
/* If OLD_NAME, which is being defined by statement STMT, is an SSA_NAME of a
|
|
parameter which is to be removed because its value is not used, create a new
|
|
SSA_NAME relating to a replacement VAR_DECL, replace all uses of the
|
|
original with it and return it. If there is no need to re-map, return NULL.
|
|
ADJUSTMENTS is a pointer to a vector of IPA-SRA adjustments. */
|
|
|
|
tree
|
|
ipa_param_body_adjustments::replace_removed_params_ssa_names (tree old_name,
|
|
gimple *stmt)
|
|
{
|
|
gcc_assert (!m_id);
|
|
if (TREE_CODE (old_name) != SSA_NAME)
|
|
return NULL;
|
|
|
|
tree decl = SSA_NAME_VAR (old_name);
|
|
if (decl == NULL_TREE
|
|
|| TREE_CODE (decl) != PARM_DECL)
|
|
return NULL;
|
|
|
|
tree repl = get_replacement_ssa_base (decl);
|
|
if (!repl)
|
|
return NULL;
|
|
|
|
tree new_name = make_ssa_name (repl, stmt);
|
|
SSA_NAME_OCCURS_IN_ABNORMAL_PHI (new_name)
|
|
= SSA_NAME_OCCURS_IN_ABNORMAL_PHI (old_name);
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "replacing an SSA name of a removed param ");
|
|
print_generic_expr (dump_file, old_name);
|
|
fprintf (dump_file, " with ");
|
|
print_generic_expr (dump_file, new_name);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
|
|
replace_uses_by (old_name, new_name);
|
|
return new_name;
|
|
}
|
|
|
|
/* If the expression *EXPR_P should be replaced, do so. CONVERT specifies
|
|
whether the function should care about type incompatibility of the current
|
|
and new expressions. If it is false, the function will leave
|
|
incompatibility issues to the caller - note that when the function
|
|
encounters a BIT_FIELD_REF, IMAGPART_EXPR or REALPART_EXPR, it will modify
|
|
their bases instead of the expressions themselves and then also performs any
|
|
necessary conversions. */
|
|
|
|
bool
|
|
ipa_param_body_adjustments::modify_expression (tree *expr_p, bool convert)
|
|
{
|
|
tree expr = *expr_p;
|
|
|
|
if (TREE_CODE (expr) == BIT_FIELD_REF
|
|
|| TREE_CODE (expr) == IMAGPART_EXPR
|
|
|| TREE_CODE (expr) == REALPART_EXPR)
|
|
{
|
|
expr_p = &TREE_OPERAND (expr, 0);
|
|
expr = *expr_p;
|
|
convert = true;
|
|
}
|
|
|
|
ipa_param_body_replacement *pbr = get_expr_replacement (expr, false);
|
|
if (!pbr)
|
|
return false;
|
|
|
|
tree repl = pbr->repl;
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "About to replace expr ");
|
|
print_generic_expr (dump_file, expr);
|
|
fprintf (dump_file, " with ");
|
|
print_generic_expr (dump_file, repl);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
|
|
if (convert && !useless_type_conversion_p (TREE_TYPE (expr),
|
|
TREE_TYPE (repl)))
|
|
{
|
|
tree vce = build1 (VIEW_CONVERT_EXPR, TREE_TYPE (expr), repl);
|
|
*expr_p = vce;
|
|
}
|
|
else
|
|
*expr_p = repl;
|
|
return true;
|
|
}
|
|
|
|
/* If the assignment statement STMT contains any expressions that need to
|
|
replaced with a different one as noted by ADJUSTMENTS, do so. Handle any
|
|
potential type incompatibilities. If any conversion sttements have to be
|
|
pre-pended to STMT, they will be added to EXTRA_STMTS. Return true iff the
|
|
statement was modified. */
|
|
|
|
bool
|
|
ipa_param_body_adjustments::modify_assignment (gimple *stmt,
|
|
gimple_seq *extra_stmts)
|
|
{
|
|
tree *lhs_p, *rhs_p;
|
|
bool any;
|
|
|
|
if (!gimple_assign_single_p (stmt))
|
|
return false;
|
|
|
|
rhs_p = gimple_assign_rhs1_ptr (stmt);
|
|
lhs_p = gimple_assign_lhs_ptr (stmt);
|
|
|
|
any = modify_expression (lhs_p, false);
|
|
any |= modify_expression (rhs_p, false);
|
|
if (any
|
|
&& !useless_type_conversion_p (TREE_TYPE (*lhs_p), TREE_TYPE (*rhs_p)))
|
|
{
|
|
if (TREE_CODE (*rhs_p) == CONSTRUCTOR)
|
|
{
|
|
/* V_C_Es of constructors can cause trouble (PR 42714). */
|
|
if (is_gimple_reg_type (TREE_TYPE (*lhs_p)))
|
|
*rhs_p = build_zero_cst (TREE_TYPE (*lhs_p));
|
|
else
|
|
*rhs_p = build_constructor (TREE_TYPE (*lhs_p),
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
tree new_rhs = fold_build1_loc (gimple_location (stmt),
|
|
VIEW_CONVERT_EXPR, TREE_TYPE (*lhs_p),
|
|
*rhs_p);
|
|
tree tmp = force_gimple_operand (new_rhs, extra_stmts, true,
|
|
NULL_TREE);
|
|
gimple_assign_set_rhs1 (stmt, tmp);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return any;
|
|
}
|
|
|
|
/* Record information about what modifications to call arguments have already
|
|
been done by clone materialization into a summary describing CS. The
|
|
information is stored in NEW_INDEX_MAP, NEW_PT_MAP and NEW_ALWAYS_COPY_DELTA
|
|
and correspond to equivalent fields in ipa_edge_modification_info. Return
|
|
the edge summary. */
|
|
|
|
static ipa_edge_modification_info *
|
|
record_argument_state_1 (cgraph_edge *cs, const vec<int> &new_index_map,
|
|
const vec<pass_through_split_map> &new_pt_map,
|
|
int new_always_copy_delta)
|
|
|
|
{
|
|
ipa_edge_modification_info *sum = ipa_edge_modifications->get_create (cs);
|
|
|
|
unsigned len = sum->pass_through_map.length ();
|
|
for (unsigned i = 0; i < len; i++)
|
|
{
|
|
unsigned oldnew = sum->pass_through_map[i].new_index;
|
|
sum->pass_through_map[i].new_index = new_index_map[oldnew];
|
|
}
|
|
|
|
len = sum->index_map.length ();
|
|
if (len > 0)
|
|
{
|
|
unsigned nptlen = new_pt_map.length ();
|
|
for (unsigned j = 0; j < nptlen; j++)
|
|
{
|
|
int inverse = -1;
|
|
for (unsigned i = 0; i < len ; i++)
|
|
if ((unsigned) sum->index_map[i] == new_pt_map[j].base_index)
|
|
{
|
|
inverse = i;
|
|
break;
|
|
}
|
|
gcc_assert (inverse >= 0);
|
|
pass_through_split_map ptm_item;
|
|
|
|
ptm_item.base_index = inverse;
|
|
ptm_item.unit_offset = new_pt_map[j].unit_offset;
|
|
ptm_item.new_index = new_pt_map[j].new_index;
|
|
sum->pass_through_map.safe_push (ptm_item);
|
|
}
|
|
|
|
for (unsigned i = 0; i < len; i++)
|
|
{
|
|
int idx = sum->index_map[i];
|
|
if (idx < 0)
|
|
continue;
|
|
sum->index_map[i] = new_index_map[idx];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sum->pass_through_map.safe_splice (new_pt_map);
|
|
sum->index_map.safe_splice (new_index_map);
|
|
}
|
|
sum->always_copy_delta += new_always_copy_delta;
|
|
return sum;
|
|
}
|
|
|
|
/* Record information about what modifications to call arguments have already
|
|
been done by clone materialization into a summary of an edge describing the
|
|
call in this clone and all its clones. NEW_INDEX_MAP, NEW_PT_MAP and
|
|
NEW_ALWAYS_COPY_DELTA have the same meaning as record_argument_state_1.
|
|
|
|
In order to associate the info with the right edge summaries, we need
|
|
address of the ORIG_STMT in the function from which we are cloning (because
|
|
the edges have not yet been re-assigned to the new statement that has just
|
|
been created) and ID, the structure governing function body copying. */
|
|
|
|
static void
|
|
record_argument_state (copy_body_data *id, gimple *orig_stmt,
|
|
const vec<int> &new_index_map,
|
|
const vec<pass_through_split_map> &new_pt_map,
|
|
int new_always_copy_delta)
|
|
{
|
|
if (!ipa_edge_modifications)
|
|
ipa_edge_modifications = new ipa_edge_modification_sum (symtab);
|
|
|
|
struct cgraph_node *this_node = id->dst_node;
|
|
ipa_edge_modification_info *first_sum = NULL;
|
|
cgraph_edge *cs = this_node->get_edge (orig_stmt);
|
|
if (cs)
|
|
first_sum = record_argument_state_1 (cs, new_index_map, new_pt_map,
|
|
new_always_copy_delta);
|
|
else
|
|
gcc_assert (this_node->clones);
|
|
|
|
if (!this_node->clones)
|
|
return;
|
|
for (cgraph_node *subclone = this_node->clones; subclone != this_node;)
|
|
{
|
|
cs = subclone->get_edge (orig_stmt);
|
|
if (cs)
|
|
{
|
|
if (!first_sum)
|
|
first_sum = record_argument_state_1 (cs, new_index_map, new_pt_map,
|
|
new_always_copy_delta);
|
|
else
|
|
{
|
|
ipa_edge_modification_info *s2
|
|
= ipa_edge_modifications->get_create (cs);
|
|
s2->index_map.truncate (0);
|
|
s2->index_map.safe_splice (first_sum->index_map);
|
|
s2->pass_through_map.truncate (0);
|
|
s2->pass_through_map.safe_splice (first_sum->pass_through_map);
|
|
s2->always_copy_delta = first_sum->always_copy_delta;
|
|
}
|
|
}
|
|
else
|
|
gcc_assert (subclone->clones);
|
|
|
|
if (subclone->clones)
|
|
subclone = subclone->clones;
|
|
else if (subclone->next_sibling_clone)
|
|
subclone = subclone->next_sibling_clone;
|
|
else
|
|
{
|
|
while (subclone != this_node && !subclone->next_sibling_clone)
|
|
subclone = subclone->clone_of;
|
|
if (subclone != this_node)
|
|
subclone = subclone->next_sibling_clone;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If the call statement pointed at by STMT_P contains any expressions that
|
|
need to replaced with a different one as noted by ADJUSTMENTS, do so. f the
|
|
statement needs to be rebuilt, do so. Return true if any modifications have
|
|
been performed. ORIG_STMT, if not NULL, is the original statement in the
|
|
function that is being cloned from, which at this point can be used to look
|
|
up call_graph edges.
|
|
|
|
If the method is invoked as a part of IPA clone materialization and if any
|
|
parameter split is pass-through, i.e. it applies to the functin that is
|
|
being modified and also to the callee of the statement, replace the
|
|
parameter passed to old callee with all of the replacement a callee might
|
|
possibly want and record the performed argument modifications in
|
|
ipa_edge_modifications. Likewise if any argument has already been left out
|
|
because it is not necessary. */
|
|
|
|
bool
|
|
ipa_param_body_adjustments::modify_call_stmt (gcall **stmt_p,
|
|
gimple *orig_stmt)
|
|
{
|
|
auto_vec <unsigned, 4> pass_through_args;
|
|
auto_vec <unsigned, 4> pass_through_pbr_indices;
|
|
auto_vec <HOST_WIDE_INT, 4> pass_through_offsets;
|
|
gcall *stmt = *stmt_p;
|
|
unsigned nargs = gimple_call_num_args (stmt);
|
|
bool recreate = false;
|
|
|
|
for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
|
|
{
|
|
tree t = gimple_call_arg (stmt, i);
|
|
gcc_assert (TREE_CODE (t) != BIT_FIELD_REF
|
|
&& TREE_CODE (t) != IMAGPART_EXPR
|
|
&& TREE_CODE (t) != REALPART_EXPR);
|
|
|
|
if (TREE_CODE (t) == SSA_NAME
|
|
&& m_dead_ssas.contains (t))
|
|
recreate = true;
|
|
|
|
if (!m_split_modifications_p)
|
|
continue;
|
|
|
|
tree base;
|
|
unsigned agg_arg_offset;
|
|
if (!isra_get_ref_base_and_offset (t, &base, &agg_arg_offset))
|
|
continue;
|
|
|
|
bool by_ref = false;
|
|
if (TREE_CODE (base) == SSA_NAME)
|
|
{
|
|
if (!SSA_NAME_IS_DEFAULT_DEF (base))
|
|
continue;
|
|
base = SSA_NAME_VAR (base);
|
|
gcc_checking_assert (base);
|
|
by_ref = true;
|
|
}
|
|
if (TREE_CODE (base) != PARM_DECL)
|
|
continue;
|
|
|
|
bool base_among_replacements = false;
|
|
unsigned j, repl_list_len = m_replacements.length ();
|
|
for (j = 0; j < repl_list_len; j++)
|
|
{
|
|
ipa_param_body_replacement *pbr = &m_replacements[j];
|
|
if (pbr->base == base)
|
|
{
|
|
base_among_replacements = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!base_among_replacements)
|
|
continue;
|
|
|
|
/* We still have to distinguish between an end-use that we have to
|
|
transform now and a pass-through, which happens in the following
|
|
two cases. */
|
|
|
|
/* TODO: After we adjust ptr_parm_has_nonarg_uses to also consider
|
|
&MEM_REF[ssa_name + offset], we will also have to detect that case
|
|
here. */
|
|
|
|
if (TREE_CODE (t) == SSA_NAME
|
|
&& SSA_NAME_IS_DEFAULT_DEF (t)
|
|
&& SSA_NAME_VAR (t)
|
|
&& TREE_CODE (SSA_NAME_VAR (t)) == PARM_DECL)
|
|
{
|
|
/* This must be a by_reference pass-through. */
|
|
recreate = true;
|
|
gcc_assert (POINTER_TYPE_P (TREE_TYPE (t)));
|
|
pass_through_args.safe_push (i);
|
|
pass_through_pbr_indices.safe_push (j);
|
|
pass_through_offsets.safe_push (agg_arg_offset);
|
|
}
|
|
else if (!by_ref && AGGREGATE_TYPE_P (TREE_TYPE (t)))
|
|
{
|
|
/* Currently IPA-SRA guarantees the aggregate access type
|
|
exactly matches in this case. So if it does not match, it is
|
|
a pass-through argument that will be sorted out at edge
|
|
redirection time. */
|
|
ipa_param_body_replacement *pbr
|
|
= lookup_replacement_1 (base, agg_arg_offset);
|
|
|
|
if (!pbr
|
|
|| (TYPE_MAIN_VARIANT (TREE_TYPE (t))
|
|
!= TYPE_MAIN_VARIANT (TREE_TYPE (pbr->repl))))
|
|
{
|
|
recreate = true;
|
|
pass_through_args.safe_push (i);
|
|
pass_through_pbr_indices.safe_push (j);
|
|
pass_through_offsets.safe_push (agg_arg_offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!recreate)
|
|
{
|
|
/* No need to rebuild the statement, let's just modify arguments
|
|
and the LHS if/as appropriate. */
|
|
bool modified = false;
|
|
for (unsigned i = 0; i < nargs; i++)
|
|
{
|
|
tree *t = gimple_call_arg_ptr (stmt, i);
|
|
modified |= modify_expression (t, true);
|
|
}
|
|
if (gimple_call_lhs (stmt))
|
|
{
|
|
tree *t = gimple_call_lhs_ptr (stmt);
|
|
modified |= modify_expression (t, false);
|
|
}
|
|
return modified;
|
|
}
|
|
|
|
auto_vec<int, 16> index_map;
|
|
auto_vec<pass_through_split_map, 4> pass_through_map;
|
|
auto_vec<tree, 16> vargs;
|
|
int always_copy_delta = 0;
|
|
unsigned pt_idx = 0;
|
|
int new_arg_idx = 0;
|
|
for (unsigned i = 0; i < nargs; i++)
|
|
{
|
|
if (pt_idx < pass_through_args.length ()
|
|
&& i == pass_through_args[pt_idx])
|
|
{
|
|
unsigned j = pass_through_pbr_indices[pt_idx];
|
|
unsigned agg_arg_offset = pass_through_offsets[pt_idx];
|
|
pt_idx++;
|
|
always_copy_delta--;
|
|
tree base = m_replacements[j].base;
|
|
|
|
/* In order to be put into SSA form, we have to push all replacements
|
|
pertaining to this parameter as parameters to the call statement.
|
|
Edge redirection will need to use edge summary to weed out the
|
|
unnecessary ones. */
|
|
unsigned repl_list_len = m_replacements.length ();
|
|
for (; j < repl_list_len; j++)
|
|
{
|
|
if (m_replacements[j].base != base)
|
|
break;
|
|
if (m_replacements[j].unit_offset < agg_arg_offset)
|
|
continue;
|
|
pass_through_split_map pt_map;
|
|
pt_map.base_index = i;
|
|
pt_map.unit_offset
|
|
= m_replacements[j].unit_offset - agg_arg_offset;
|
|
pt_map.new_index = new_arg_idx;
|
|
pass_through_map.safe_push (pt_map);
|
|
vargs.safe_push (m_replacements[j].repl);
|
|
new_arg_idx++;
|
|
always_copy_delta++;
|
|
}
|
|
index_map.safe_push (-1);
|
|
}
|
|
else
|
|
{
|
|
tree t = gimple_call_arg (stmt, i);
|
|
if (TREE_CODE (t) == SSA_NAME
|
|
&& m_dead_ssas.contains (t))
|
|
{
|
|
always_copy_delta--;
|
|
index_map.safe_push (-1);
|
|
}
|
|
else
|
|
{
|
|
modify_expression (&t, true);
|
|
vargs.safe_push (t);
|
|
index_map.safe_push (new_arg_idx);
|
|
new_arg_idx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
gcall *new_stmt = gimple_build_call_vec (gimple_call_fn (stmt), vargs);
|
|
if (gimple_has_location (stmt))
|
|
gimple_set_location (new_stmt, gimple_location (stmt));
|
|
gimple_call_set_chain (new_stmt, gimple_call_chain (stmt));
|
|
gimple_call_copy_flags (new_stmt, stmt);
|
|
if (tree lhs = gimple_call_lhs (stmt))
|
|
{
|
|
modify_expression (&lhs, false);
|
|
/* Avoid adjusting SSA_NAME_DEF_STMT of a SSA lhs, SSA names
|
|
have not yet been remapped. */
|
|
*gimple_call_lhs_ptr (new_stmt) = lhs;
|
|
}
|
|
*stmt_p = new_stmt;
|
|
|
|
if (orig_stmt)
|
|
record_argument_state (m_id, orig_stmt, index_map, pass_through_map,
|
|
always_copy_delta);
|
|
return true;
|
|
}
|
|
|
|
/* If the statement STMT contains any expressions that need to replaced with a
|
|
different one as noted by ADJUSTMENTS, do so. Handle any potential type
|
|
incompatibilities. If any conversion sttements have to be pre-pended to
|
|
STMT, they will be added to EXTRA_STMTS. Return true iff the statement was
|
|
modified. */
|
|
|
|
bool
|
|
ipa_param_body_adjustments::modify_gimple_stmt (gimple **stmt,
|
|
gimple_seq *extra_stmts,
|
|
gimple *orig_stmt)
|
|
{
|
|
bool modified = false;
|
|
tree *t;
|
|
|
|
switch (gimple_code (*stmt))
|
|
{
|
|
case GIMPLE_RETURN:
|
|
t = gimple_return_retval_ptr (as_a <greturn *> (*stmt));
|
|
if (m_adjustments && m_adjustments->m_skip_return)
|
|
*t = NULL_TREE;
|
|
else if (*t != NULL_TREE)
|
|
modified |= modify_expression (t, true);
|
|
break;
|
|
|
|
case GIMPLE_ASSIGN:
|
|
modified |= modify_assignment (*stmt, extra_stmts);
|
|
break;
|
|
|
|
case GIMPLE_CALL:
|
|
modified |= modify_call_stmt ((gcall **) stmt, orig_stmt);
|
|
break;
|
|
|
|
case GIMPLE_ASM:
|
|
{
|
|
gasm *asm_stmt = as_a <gasm *> (*stmt);
|
|
for (unsigned i = 0; i < gimple_asm_ninputs (asm_stmt); i++)
|
|
{
|
|
t = &TREE_VALUE (gimple_asm_input_op (asm_stmt, i));
|
|
modified |= modify_expression (t, true);
|
|
}
|
|
for (unsigned i = 0; i < gimple_asm_noutputs (asm_stmt); i++)
|
|
{
|
|
t = &TREE_VALUE (gimple_asm_output_op (asm_stmt, i));
|
|
modified |= modify_expression (t, false);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return modified;
|
|
}
|
|
|
|
|
|
/* Traverse body of the current function and perform the requested adjustments
|
|
on its statements. Return true iff the CFG has been changed. */
|
|
|
|
bool
|
|
ipa_param_body_adjustments::modify_cfun_body ()
|
|
{
|
|
bool cfg_changed = false;
|
|
basic_block bb;
|
|
|
|
FOR_EACH_BB_FN (bb, cfun)
|
|
{
|
|
gimple_stmt_iterator gsi;
|
|
|
|
for (gsi = gsi_start_phis (bb); !gsi_end_p (gsi); gsi_next (&gsi))
|
|
{
|
|
gphi *phi = as_a <gphi *> (gsi_stmt (gsi));
|
|
tree new_lhs, old_lhs = gimple_phi_result (phi);
|
|
new_lhs = replace_removed_params_ssa_names (old_lhs, phi);
|
|
if (new_lhs)
|
|
{
|
|
gimple_phi_set_result (phi, new_lhs);
|
|
release_ssa_name (old_lhs);
|
|
}
|
|
}
|
|
|
|
gsi = gsi_start_bb (bb);
|
|
while (!gsi_end_p (gsi))
|
|
{
|
|
gimple *stmt = gsi_stmt (gsi);
|
|
gimple *stmt_copy = stmt;
|
|
gimple_seq extra_stmts = NULL;
|
|
bool modified = modify_gimple_stmt (&stmt, &extra_stmts, NULL);
|
|
if (stmt != stmt_copy)
|
|
{
|
|
gcc_checking_assert (modified);
|
|
gsi_replace (&gsi, stmt, false);
|
|
}
|
|
if (!gimple_seq_empty_p (extra_stmts))
|
|
gsi_insert_seq_before (&gsi, extra_stmts, GSI_SAME_STMT);
|
|
|
|
def_operand_p defp;
|
|
ssa_op_iter iter;
|
|
FOR_EACH_SSA_DEF_OPERAND (defp, stmt, iter, SSA_OP_DEF)
|
|
{
|
|
tree old_def = DEF_FROM_PTR (defp);
|
|
if (tree new_def = replace_removed_params_ssa_names (old_def,
|
|
stmt))
|
|
{
|
|
SET_DEF (defp, new_def);
|
|
release_ssa_name (old_def);
|
|
modified = true;
|
|
}
|
|
}
|
|
|
|
if (modified)
|
|
{
|
|
update_stmt (stmt);
|
|
if (maybe_clean_eh_stmt (stmt)
|
|
&& gimple_purge_dead_eh_edges (gimple_bb (stmt)))
|
|
cfg_changed = true;
|
|
}
|
|
gsi_next (&gsi);
|
|
}
|
|
}
|
|
|
|
return cfg_changed;
|
|
}
|
|
|
|
/* Call gimple_debug_bind_reset_value on all debug statements describing
|
|
gimple register parameters that are being removed or replaced. */
|
|
|
|
void
|
|
ipa_param_body_adjustments::reset_debug_stmts ()
|
|
{
|
|
int i, len;
|
|
gimple_stmt_iterator *gsip = NULL, gsi;
|
|
|
|
if (MAY_HAVE_DEBUG_STMTS && single_succ_p (ENTRY_BLOCK_PTR_FOR_FN (cfun)))
|
|
{
|
|
gsi = gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
|
|
gsip = &gsi;
|
|
}
|
|
len = m_reset_debug_decls.length ();
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
imm_use_iterator ui;
|
|
gimple *stmt;
|
|
gdebug *def_temp;
|
|
tree name, vexpr, copy = NULL_TREE;
|
|
use_operand_p use_p;
|
|
tree decl = m_reset_debug_decls[i];
|
|
|
|
gcc_checking_assert (is_gimple_reg (decl));
|
|
name = ssa_default_def (cfun, decl);
|
|
vexpr = NULL;
|
|
if (name)
|
|
FOR_EACH_IMM_USE_STMT (stmt, ui, name)
|
|
{
|
|
if (gimple_clobber_p (stmt))
|
|
{
|
|
gimple_stmt_iterator cgsi = gsi_for_stmt (stmt);
|
|
unlink_stmt_vdef (stmt);
|
|
gsi_remove (&cgsi, true);
|
|
release_defs (stmt);
|
|
continue;
|
|
}
|
|
/* All other users must have been removed by function body
|
|
modification. */
|
|
gcc_assert (is_gimple_debug (stmt));
|
|
if (vexpr == NULL && gsip != NULL)
|
|
{
|
|
vexpr = build_debug_expr_decl (TREE_TYPE (name));
|
|
/* FIXME: Is setting the mode really necessary? */
|
|
SET_DECL_MODE (vexpr, DECL_MODE (decl));
|
|
def_temp = gimple_build_debug_source_bind (vexpr, decl, NULL);
|
|
gsi_insert_before (gsip, def_temp, GSI_SAME_STMT);
|
|
}
|
|
if (vexpr)
|
|
{
|
|
FOR_EACH_IMM_USE_ON_STMT (use_p, ui)
|
|
SET_USE (use_p, vexpr);
|
|
}
|
|
else
|
|
gimple_debug_bind_reset_value (stmt);
|
|
update_stmt (stmt);
|
|
}
|
|
/* Create a VAR_DECL for debug info purposes. */
|
|
if (!DECL_IGNORED_P (decl))
|
|
{
|
|
copy = build_decl (DECL_SOURCE_LOCATION (current_function_decl),
|
|
VAR_DECL, DECL_NAME (decl),
|
|
TREE_TYPE (decl));
|
|
if (DECL_PT_UID_SET_P (decl))
|
|
SET_DECL_PT_UID (copy, DECL_PT_UID (decl));
|
|
TREE_ADDRESSABLE (copy) = TREE_ADDRESSABLE (decl);
|
|
TREE_READONLY (copy) = TREE_READONLY (decl);
|
|
TREE_THIS_VOLATILE (copy) = TREE_THIS_VOLATILE (decl);
|
|
DECL_NOT_GIMPLE_REG_P (copy) = DECL_NOT_GIMPLE_REG_P (decl);
|
|
DECL_ARTIFICIAL (copy) = DECL_ARTIFICIAL (decl);
|
|
DECL_IGNORED_P (copy) = DECL_IGNORED_P (decl);
|
|
DECL_ABSTRACT_ORIGIN (copy) = DECL_ORIGIN (decl);
|
|
DECL_SEEN_IN_BIND_EXPR_P (copy) = 1;
|
|
SET_DECL_RTL (copy, 0);
|
|
TREE_USED (copy) = 1;
|
|
DECL_CONTEXT (copy) = current_function_decl;
|
|
add_local_decl (cfun, copy);
|
|
DECL_CHAIN (copy)
|
|
= BLOCK_VARS (DECL_INITIAL (current_function_decl));
|
|
BLOCK_VARS (DECL_INITIAL (current_function_decl)) = copy;
|
|
}
|
|
if (gsip != NULL && copy && target_for_debug_bind (decl))
|
|
{
|
|
gcc_assert (TREE_CODE (decl) == PARM_DECL);
|
|
if (vexpr)
|
|
def_temp = gimple_build_debug_bind (copy, vexpr, NULL);
|
|
else
|
|
def_temp = gimple_build_debug_source_bind (copy, decl,
|
|
NULL);
|
|
gsi_insert_before (gsip, def_temp, GSI_SAME_STMT);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Perform all necessary body changes to change signature, body and debug info
|
|
of fun according to adjustments passed at construction. Return true if CFG
|
|
was changed in any way. The main entry point for modification of standalone
|
|
functions that is not part of IPA clone materialization. */
|
|
|
|
bool
|
|
ipa_param_body_adjustments::perform_cfun_body_modifications ()
|
|
{
|
|
bool cfg_changed;
|
|
modify_formal_parameters ();
|
|
cfg_changed = modify_cfun_body ();
|
|
reset_debug_stmts ();
|
|
|
|
return cfg_changed;
|
|
}
|
|
|
|
|
|
/* Deallocate summaries which otherwise stay alive until the end of
|
|
compilation. */
|
|
|
|
void
|
|
ipa_edge_modifications_finalize ()
|
|
{
|
|
if (!ipa_edge_modifications)
|
|
return;
|
|
delete ipa_edge_modifications;
|
|
ipa_edge_modifications = NULL;
|
|
}
|
|
|
|
|