gcc/gcc/tree-stdarg.c
Richard Biener 8d4c374c44 tree-optimization/99912 - schedule another TODO_remove_unused_locals
This makes sure to remove unused locals and prune CLOBBERs after
the first scalar cleanup phase after IPA optimizations.  On the
testcase in the PR this results in 8000 CLOBBERs removed which
in turn unleashes more DSE which otherwise hits its walking limit
of 256 too early on this testcase.

2021-04-27  Richard Biener  <rguenther@suse.de>

	PR tree-optimization/99912
	* passes.def: Add comment about new TODO_remove_unused_locals.
	* tree-stdarg.c (pass_data_stdarg): Run TODO_remove_unused_locals
	at start.
2021-04-27 15:17:34 +02:00

1211 lines
31 KiB
C

/* Pass computing data for optimizing stdarg functions.
Copyright (C) 2004-2021 Free Software Foundation, Inc.
Contributed by Jakub Jelinek <jakub@redhat.com>
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 "target.h"
#include "tree.h"
#include "gimple.h"
#include "tree-pass.h"
#include "ssa.h"
#include "gimple-pretty-print.h"
#include "fold-const.h"
#include "langhooks.h"
#include "gimple-iterator.h"
#include "gimple-walk.h"
#include "gimplify.h"
#include "tree-into-ssa.h"
#include "tree-cfg.h"
#include "tree-stdarg.h"
/* A simple pass that attempts to optimize stdarg functions on architectures
that need to save register arguments to stack on entry to stdarg functions.
If the function doesn't use any va_start macros, no registers need to
be saved. If va_start macros are used, the va_list variables don't escape
the function, it is only necessary to save registers that will be used
in va_arg macros. E.g. if va_arg is only used with integral types
in the function, floating point registers don't need to be saved, etc. */
/* Return true if basic block VA_ARG_BB is dominated by VA_START_BB and
is executed at most as many times as VA_START_BB. */
static bool
reachable_at_most_once (basic_block va_arg_bb, basic_block va_start_bb)
{
auto_vec<edge, 10> stack;
edge e;
edge_iterator ei;
bool ret;
if (va_arg_bb == va_start_bb)
return true;
if (! dominated_by_p (CDI_DOMINATORS, va_arg_bb, va_start_bb))
return false;
auto_sbitmap visited (last_basic_block_for_fn (cfun));
bitmap_clear (visited);
ret = true;
FOR_EACH_EDGE (e, ei, va_arg_bb->preds)
stack.safe_push (e);
while (! stack.is_empty ())
{
basic_block src;
e = stack.pop ();
src = e->src;
if (e->flags & EDGE_COMPLEX)
{
ret = false;
break;
}
if (src == va_start_bb)
continue;
/* va_arg_bb can be executed more times than va_start_bb. */
if (src == va_arg_bb)
{
ret = false;
break;
}
gcc_assert (src != ENTRY_BLOCK_PTR_FOR_FN (cfun));
if (! bitmap_bit_p (visited, src->index))
{
bitmap_set_bit (visited, src->index);
FOR_EACH_EDGE (e, ei, src->preds)
stack.safe_push (e);
}
}
return ret;
}
/* For statement COUNTER = RHS, if RHS is COUNTER + constant,
return constant, otherwise return HOST_WIDE_INT_M1U.
GPR_P is true if this is GPR counter. */
static unsigned HOST_WIDE_INT
va_list_counter_bump (struct stdarg_info *si, tree counter, tree rhs,
bool gpr_p)
{
tree lhs, orig_lhs;
gimple *stmt;
unsigned HOST_WIDE_INT ret = 0, val, counter_val;
unsigned int max_size;
if (si->offsets == NULL)
{
unsigned int i;
si->offsets = XNEWVEC (int, num_ssa_names);
for (i = 0; i < num_ssa_names; ++i)
si->offsets[i] = -1;
}
counter_val = gpr_p ? cfun->va_list_gpr_size : cfun->va_list_fpr_size;
max_size = gpr_p ? VA_LIST_MAX_GPR_SIZE : VA_LIST_MAX_FPR_SIZE;
orig_lhs = lhs = rhs;
while (lhs)
{
enum tree_code rhs_code;
tree rhs1;
if (si->offsets[SSA_NAME_VERSION (lhs)] != -1)
{
if (counter_val >= max_size)
{
ret = max_size;
break;
}
ret -= counter_val - si->offsets[SSA_NAME_VERSION (lhs)];
break;
}
stmt = SSA_NAME_DEF_STMT (lhs);
if (!is_gimple_assign (stmt) || gimple_assign_lhs (stmt) != lhs)
return HOST_WIDE_INT_M1U;
rhs_code = gimple_assign_rhs_code (stmt);
rhs1 = gimple_assign_rhs1 (stmt);
if ((get_gimple_rhs_class (rhs_code) == GIMPLE_SINGLE_RHS
|| gimple_assign_cast_p (stmt))
&& TREE_CODE (rhs1) == SSA_NAME)
{
lhs = rhs1;
continue;
}
if ((rhs_code == POINTER_PLUS_EXPR
|| rhs_code == PLUS_EXPR)
&& TREE_CODE (rhs1) == SSA_NAME
&& tree_fits_uhwi_p (gimple_assign_rhs2 (stmt)))
{
ret += tree_to_uhwi (gimple_assign_rhs2 (stmt));
lhs = rhs1;
continue;
}
if (rhs_code == ADDR_EXPR
&& TREE_CODE (TREE_OPERAND (rhs1, 0)) == MEM_REF
&& TREE_CODE (TREE_OPERAND (TREE_OPERAND (rhs1, 0), 0)) == SSA_NAME
&& tree_fits_uhwi_p (TREE_OPERAND (TREE_OPERAND (rhs1, 0), 1)))
{
ret += tree_to_uhwi (TREE_OPERAND (TREE_OPERAND (rhs1, 0), 1));
lhs = TREE_OPERAND (TREE_OPERAND (rhs1, 0), 0);
continue;
}
if (get_gimple_rhs_class (rhs_code) != GIMPLE_SINGLE_RHS)
return HOST_WIDE_INT_M1U;
rhs = gimple_assign_rhs1 (stmt);
if (TREE_CODE (counter) != TREE_CODE (rhs))
return HOST_WIDE_INT_M1U;
if (TREE_CODE (counter) == COMPONENT_REF)
{
if (get_base_address (counter) != get_base_address (rhs)
|| TREE_CODE (TREE_OPERAND (rhs, 1)) != FIELD_DECL
|| TREE_OPERAND (counter, 1) != TREE_OPERAND (rhs, 1))
return HOST_WIDE_INT_M1U;
}
else if (counter != rhs)
return HOST_WIDE_INT_M1U;
lhs = NULL;
}
lhs = orig_lhs;
val = ret + counter_val;
while (lhs)
{
enum tree_code rhs_code;
tree rhs1;
if (si->offsets[SSA_NAME_VERSION (lhs)] != -1)
break;
if (val >= max_size)
si->offsets[SSA_NAME_VERSION (lhs)] = max_size;
else
si->offsets[SSA_NAME_VERSION (lhs)] = val;
stmt = SSA_NAME_DEF_STMT (lhs);
rhs_code = gimple_assign_rhs_code (stmt);
rhs1 = gimple_assign_rhs1 (stmt);
if ((get_gimple_rhs_class (rhs_code) == GIMPLE_SINGLE_RHS
|| gimple_assign_cast_p (stmt))
&& TREE_CODE (rhs1) == SSA_NAME)
{
lhs = rhs1;
continue;
}
if ((rhs_code == POINTER_PLUS_EXPR
|| rhs_code == PLUS_EXPR)
&& TREE_CODE (rhs1) == SSA_NAME
&& tree_fits_uhwi_p (gimple_assign_rhs2 (stmt)))
{
val -= tree_to_uhwi (gimple_assign_rhs2 (stmt));
lhs = rhs1;
continue;
}
if (rhs_code == ADDR_EXPR
&& TREE_CODE (TREE_OPERAND (rhs1, 0)) == MEM_REF
&& TREE_CODE (TREE_OPERAND (TREE_OPERAND (rhs1, 0), 0)) == SSA_NAME
&& tree_fits_uhwi_p (TREE_OPERAND (TREE_OPERAND (rhs1, 0), 1)))
{
val -= tree_to_uhwi (TREE_OPERAND (TREE_OPERAND (rhs1, 0), 1));
lhs = TREE_OPERAND (TREE_OPERAND (rhs1, 0), 0);
continue;
}
lhs = NULL;
}
return ret;
}
/* Called by walk_tree to look for references to va_list variables. */
static tree
find_va_list_reference (tree *tp, int *walk_subtrees ATTRIBUTE_UNUSED,
void *data)
{
bitmap va_list_vars = (bitmap) ((struct walk_stmt_info *) data)->info;
tree var = *tp;
if (TREE_CODE (var) == SSA_NAME)
{
if (bitmap_bit_p (va_list_vars, SSA_NAME_VERSION (var)))
return var;
}
else if (VAR_P (var))
{
if (bitmap_bit_p (va_list_vars, DECL_UID (var) + num_ssa_names))
return var;
}
return NULL_TREE;
}
/* Helper function of va_list_counter_struct_op. Compute
cfun->va_list_{g,f}pr_size. AP is a va_list GPR/FPR counter,
if WRITE_P is true, seen in AP = VAR, otherwise seen in VAR = AP
statement. GPR_P is true if AP is a GPR counter, false if it is
a FPR counter. */
static void
va_list_counter_op (struct stdarg_info *si, tree ap, tree var, bool gpr_p,
bool write_p)
{
unsigned HOST_WIDE_INT increment;
if (si->compute_sizes < 0)
{
si->compute_sizes = 0;
if (si->va_start_count == 1
&& reachable_at_most_once (si->bb, si->va_start_bb))
si->compute_sizes = 1;
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file,
"bb%d will %sbe executed at most once for each va_start "
"in bb%d\n", si->bb->index, si->compute_sizes ? "" : "not ",
si->va_start_bb->index);
}
if (write_p
&& si->compute_sizes
&& (increment = va_list_counter_bump (si, ap, var, gpr_p)) + 1 > 1)
{
if (gpr_p && cfun->va_list_gpr_size + increment < VA_LIST_MAX_GPR_SIZE)
{
cfun->va_list_gpr_size += increment;
return;
}
if (!gpr_p && cfun->va_list_fpr_size + increment < VA_LIST_MAX_FPR_SIZE)
{
cfun->va_list_fpr_size += increment;
return;
}
}
if (write_p || !si->compute_sizes)
{
if (gpr_p)
cfun->va_list_gpr_size = VA_LIST_MAX_GPR_SIZE;
else
cfun->va_list_fpr_size = VA_LIST_MAX_FPR_SIZE;
}
}
/* If AP is a va_list GPR/FPR counter, compute cfun->va_list_{g,f}pr_size.
If WRITE_P is true, AP has been seen in AP = VAR assignment, if WRITE_P
is false, AP has been seen in VAR = AP assignment.
Return true if the AP = VAR (resp. VAR = AP) statement is a recognized
va_arg operation that doesn't cause the va_list variable to escape
current function. */
static bool
va_list_counter_struct_op (struct stdarg_info *si, tree ap, tree var,
bool write_p)
{
tree base;
if (TREE_CODE (ap) != COMPONENT_REF
|| TREE_CODE (TREE_OPERAND (ap, 1)) != FIELD_DECL)
return false;
if (TREE_CODE (var) != SSA_NAME
|| bitmap_bit_p (si->va_list_vars, SSA_NAME_VERSION (var)))
return false;
base = get_base_address (ap);
if (!VAR_P (base)
|| !bitmap_bit_p (si->va_list_vars, DECL_UID (base) + num_ssa_names))
return false;
if (TREE_OPERAND (ap, 1) == va_list_gpr_counter_field)
va_list_counter_op (si, ap, var, true, write_p);
else if (TREE_OPERAND (ap, 1) == va_list_fpr_counter_field)
va_list_counter_op (si, ap, var, false, write_p);
return true;
}
/* Check for TEM = AP. Return true if found and the caller shouldn't
search for va_list references in the statement. */
static bool
va_list_ptr_read (struct stdarg_info *si, tree ap, tree tem)
{
if (!VAR_P (ap)
|| !bitmap_bit_p (si->va_list_vars, DECL_UID (ap) + num_ssa_names))
return false;
if (TREE_CODE (tem) != SSA_NAME
|| bitmap_bit_p (si->va_list_vars, SSA_NAME_VERSION (tem)))
return false;
if (si->compute_sizes < 0)
{
si->compute_sizes = 0;
if (si->va_start_count == 1
&& reachable_at_most_once (si->bb, si->va_start_bb))
si->compute_sizes = 1;
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file,
"bb%d will %sbe executed at most once for each va_start "
"in bb%d\n", si->bb->index, si->compute_sizes ? "" : "not ",
si->va_start_bb->index);
}
/* For void * or char * va_list types, there is just one counter.
If va_arg is used in a loop, we don't know how many registers need
saving. */
if (! si->compute_sizes)
return false;
if (va_list_counter_bump (si, ap, tem, true) == HOST_WIDE_INT_M1U)
return false;
/* Note the temporary, as we need to track whether it doesn't escape
the current function. */
bitmap_set_bit (si->va_list_escape_vars, SSA_NAME_VERSION (tem));
return true;
}
/* Check for:
tem1 = AP;
TEM2 = tem1 + CST;
AP = TEM2;
sequence and update cfun->va_list_gpr_size. Return true if found. */
static bool
va_list_ptr_write (struct stdarg_info *si, tree ap, tree tem2)
{
unsigned HOST_WIDE_INT increment;
if (!VAR_P (ap)
|| !bitmap_bit_p (si->va_list_vars, DECL_UID (ap) + num_ssa_names))
return false;
if (TREE_CODE (tem2) != SSA_NAME
|| bitmap_bit_p (si->va_list_vars, SSA_NAME_VERSION (tem2)))
return false;
if (si->compute_sizes <= 0)
return false;
increment = va_list_counter_bump (si, ap, tem2, true);
if (increment + 1 <= 1)
return false;
if (cfun->va_list_gpr_size + increment < VA_LIST_MAX_GPR_SIZE)
cfun->va_list_gpr_size += increment;
else
cfun->va_list_gpr_size = VA_LIST_MAX_GPR_SIZE;
return true;
}
/* If RHS is X, (some type *) X or X + CST for X a temporary variable
containing value of some va_list variable plus optionally some constant,
either set si->va_list_escapes or add LHS to si->va_list_escape_vars,
depending whether LHS is a function local temporary. */
static void
check_va_list_escapes (struct stdarg_info *si, tree lhs, tree rhs)
{
if (! POINTER_TYPE_P (TREE_TYPE (rhs)))
return;
if (TREE_CODE (rhs) == SSA_NAME)
{
if (! bitmap_bit_p (si->va_list_escape_vars, SSA_NAME_VERSION (rhs)))
return;
}
else if (TREE_CODE (rhs) == ADDR_EXPR
&& TREE_CODE (TREE_OPERAND (rhs, 0)) == MEM_REF
&& TREE_CODE (TREE_OPERAND (TREE_OPERAND (rhs, 0), 0)) == SSA_NAME)
{
tree ptr = TREE_OPERAND (TREE_OPERAND (rhs, 0), 0);
if (! bitmap_bit_p (si->va_list_escape_vars, SSA_NAME_VERSION (ptr)))
return;
}
else
return;
if (TREE_CODE (lhs) != SSA_NAME)
{
si->va_list_escapes = true;
return;
}
if (si->compute_sizes < 0)
{
si->compute_sizes = 0;
if (si->va_start_count == 1
&& reachable_at_most_once (si->bb, si->va_start_bb))
si->compute_sizes = 1;
if (dump_file && (dump_flags & TDF_DETAILS))
fprintf (dump_file,
"bb%d will %sbe executed at most once for each va_start "
"in bb%d\n", si->bb->index, si->compute_sizes ? "" : "not ",
si->va_start_bb->index);
}
/* For void * or char * va_list types, there is just one counter.
If va_arg is used in a loop, we don't know how many registers need
saving. */
if (! si->compute_sizes)
{
si->va_list_escapes = true;
return;
}
if (va_list_counter_bump (si, si->va_start_ap, lhs, true)
== HOST_WIDE_INT_M1U)
{
si->va_list_escapes = true;
return;
}
bitmap_set_bit (si->va_list_escape_vars, SSA_NAME_VERSION (lhs));
}
/* Check all uses of temporaries from si->va_list_escape_vars bitmap.
Return true if va_list might be escaping. */
static bool
check_all_va_list_escapes (struct stdarg_info *si)
{
basic_block bb;
FOR_EACH_BB_FN (bb, cfun)
{
for (gphi_iterator i = gsi_start_phis (bb); !gsi_end_p (i);
gsi_next (&i))
{
tree lhs;
use_operand_p uop;
ssa_op_iter soi;
gphi *phi = i.phi ();
lhs = PHI_RESULT (phi);
if (virtual_operand_p (lhs)
|| bitmap_bit_p (si->va_list_escape_vars,
SSA_NAME_VERSION (lhs)))
continue;
FOR_EACH_PHI_ARG (uop, phi, soi, SSA_OP_USE)
{
tree rhs = USE_FROM_PTR (uop);
if (TREE_CODE (rhs) == SSA_NAME
&& bitmap_bit_p (si->va_list_escape_vars,
SSA_NAME_VERSION (rhs)))
{
if (dump_file && (dump_flags & TDF_DETAILS))
{
fputs ("va_list escapes in ", dump_file);
print_gimple_stmt (dump_file, phi, 0, dump_flags);
fputc ('\n', dump_file);
}
return true;
}
}
}
for (gimple_stmt_iterator i = gsi_start_bb (bb); !gsi_end_p (i);
gsi_next (&i))
{
gimple *stmt = gsi_stmt (i);
tree use;
ssa_op_iter iter;
if (is_gimple_debug (stmt))
continue;
FOR_EACH_SSA_TREE_OPERAND (use, stmt, iter, SSA_OP_ALL_USES)
{
if (! bitmap_bit_p (si->va_list_escape_vars,
SSA_NAME_VERSION (use)))
continue;
if (is_gimple_assign (stmt))
{
tree rhs = gimple_assign_rhs1 (stmt);
enum tree_code rhs_code = gimple_assign_rhs_code (stmt);
/* x = *ap_temp; */
if (rhs_code == MEM_REF
&& TREE_OPERAND (rhs, 0) == use
&& TYPE_SIZE_UNIT (TREE_TYPE (rhs))
&& tree_fits_uhwi_p (TYPE_SIZE_UNIT (TREE_TYPE (rhs)))
&& si->offsets[SSA_NAME_VERSION (use)] != -1)
{
unsigned HOST_WIDE_INT gpr_size;
tree access_size = TYPE_SIZE_UNIT (TREE_TYPE (rhs));
gpr_size = si->offsets[SSA_NAME_VERSION (use)]
+ tree_to_shwi (TREE_OPERAND (rhs, 1))
+ tree_to_uhwi (access_size);
if (gpr_size >= VA_LIST_MAX_GPR_SIZE)
cfun->va_list_gpr_size = VA_LIST_MAX_GPR_SIZE;
else if (gpr_size > cfun->va_list_gpr_size)
cfun->va_list_gpr_size = gpr_size;
continue;
}
/* va_arg sequences may contain
other_ap_temp = ap_temp;
other_ap_temp = ap_temp + constant;
other_ap_temp = (some_type *) ap_temp;
ap = ap_temp;
statements. */
if (rhs == use
&& ((rhs_code == POINTER_PLUS_EXPR
&& (TREE_CODE (gimple_assign_rhs2 (stmt))
== INTEGER_CST))
|| gimple_assign_cast_p (stmt)
|| (get_gimple_rhs_class (rhs_code)
== GIMPLE_SINGLE_RHS)))
{
tree lhs = gimple_assign_lhs (stmt);
if (TREE_CODE (lhs) == SSA_NAME
&& bitmap_bit_p (si->va_list_escape_vars,
SSA_NAME_VERSION (lhs)))
continue;
if (VAR_P (lhs)
&& bitmap_bit_p (si->va_list_vars,
DECL_UID (lhs) + num_ssa_names))
continue;
}
else if (rhs_code == ADDR_EXPR
&& TREE_CODE (TREE_OPERAND (rhs, 0)) == MEM_REF
&& TREE_OPERAND (TREE_OPERAND (rhs, 0), 0) == use)
{
tree lhs = gimple_assign_lhs (stmt);
if (bitmap_bit_p (si->va_list_escape_vars,
SSA_NAME_VERSION (lhs)))
continue;
}
}
if (dump_file && (dump_flags & TDF_DETAILS))
{
fputs ("va_list escapes in ", dump_file);
print_gimple_stmt (dump_file, stmt, 0, dump_flags);
fputc ('\n', dump_file);
}
return true;
}
}
}
return false;
}
/* Optimize FUN->va_list_gpr_size and FUN->va_list_fpr_size. */
static void
optimize_va_list_gpr_fpr_size (function *fun)
{
basic_block bb;
bool va_list_escapes = false;
bool va_list_simple_ptr;
struct stdarg_info si;
struct walk_stmt_info wi;
const char *funcname = NULL;
tree cfun_va_list;
fun->va_list_gpr_size = 0;
fun->va_list_fpr_size = 0;
memset (&si, 0, sizeof (si));
si.va_list_vars = BITMAP_ALLOC (NULL);
si.va_list_escape_vars = BITMAP_ALLOC (NULL);
if (dump_file)
funcname = lang_hooks.decl_printable_name (current_function_decl, 2);
cfun_va_list = targetm.fn_abi_va_list (fun->decl);
va_list_simple_ptr = POINTER_TYPE_P (cfun_va_list)
&& (TREE_TYPE (cfun_va_list) == void_type_node
|| TREE_TYPE (cfun_va_list) == char_type_node);
gcc_assert (is_gimple_reg_type (cfun_va_list) == va_list_simple_ptr);
FOR_EACH_BB_FN (bb, fun)
{
gimple_stmt_iterator i;
for (i = gsi_start_bb (bb); !gsi_end_p (i); gsi_next (&i))
{
gimple *stmt = gsi_stmt (i);
tree callee, ap;
if (!is_gimple_call (stmt))
continue;
callee = gimple_call_fndecl (stmt);
if (!callee
|| !fndecl_built_in_p (callee, BUILT_IN_NORMAL))
continue;
switch (DECL_FUNCTION_CODE (callee))
{
case BUILT_IN_VA_START:
break;
/* If old style builtins are used, don't optimize anything. */
case BUILT_IN_SAVEREGS:
case BUILT_IN_NEXT_ARG:
va_list_escapes = true;
continue;
default:
continue;
}
si.va_start_count++;
ap = gimple_call_arg (stmt, 0);
if (TREE_CODE (ap) != ADDR_EXPR)
{
va_list_escapes = true;
break;
}
ap = TREE_OPERAND (ap, 0);
if (TREE_CODE (ap) == ARRAY_REF)
{
if (! integer_zerop (TREE_OPERAND (ap, 1)))
{
va_list_escapes = true;
break;
}
ap = TREE_OPERAND (ap, 0);
}
if (TYPE_MAIN_VARIANT (TREE_TYPE (ap))
!= TYPE_MAIN_VARIANT (targetm.fn_abi_va_list (fun->decl))
|| !VAR_P (ap))
{
va_list_escapes = true;
break;
}
if (is_global_var (ap))
{
va_list_escapes = true;
break;
}
bitmap_set_bit (si.va_list_vars, DECL_UID (ap) + num_ssa_names);
/* VA_START_BB and VA_START_AP will be only used if there is just
one va_start in the function. */
si.va_start_bb = bb;
si.va_start_ap = ap;
}
if (va_list_escapes)
break;
}
/* If there were no va_start uses in the function, there is no need to
save anything. */
if (si.va_start_count == 0)
goto finish;
/* If some va_list arguments weren't local, we can't optimize. */
if (va_list_escapes)
goto finish;
/* For void * or char * va_list, something useful can be done only
if there is just one va_start. */
if (va_list_simple_ptr && si.va_start_count > 1)
{
va_list_escapes = true;
goto finish;
}
/* For struct * va_list, if the backend didn't tell us what the counter fields
are, there is nothing more we can do. */
if (!va_list_simple_ptr
&& va_list_gpr_counter_field == NULL_TREE
&& va_list_fpr_counter_field == NULL_TREE)
{
va_list_escapes = true;
goto finish;
}
/* For void * or char * va_list there is just one counter
(va_list itself). Use VA_LIST_GPR_SIZE for it. */
if (va_list_simple_ptr)
fun->va_list_fpr_size = VA_LIST_MAX_FPR_SIZE;
calculate_dominance_info (CDI_DOMINATORS);
memset (&wi, 0, sizeof (wi));
wi.info = si.va_list_vars;
FOR_EACH_BB_FN (bb, fun)
{
si.compute_sizes = -1;
si.bb = bb;
/* For va_list_simple_ptr, we have to check PHI nodes too. We treat
them as assignments for the purpose of escape analysis. This is
not needed for non-simple va_list because virtual phis don't perform
any real data movement. Also, check PHI nodes for taking address of
the va_list vars. */
tree lhs, rhs;
use_operand_p uop;
ssa_op_iter soi;
for (gphi_iterator i = gsi_start_phis (bb); !gsi_end_p (i);
gsi_next (&i))
{
gphi *phi = i.phi ();
lhs = PHI_RESULT (phi);
if (virtual_operand_p (lhs))
continue;
if (va_list_simple_ptr)
{
FOR_EACH_PHI_ARG (uop, phi, soi, SSA_OP_USE)
{
rhs = USE_FROM_PTR (uop);
if (va_list_ptr_read (&si, rhs, lhs))
continue;
else if (va_list_ptr_write (&si, lhs, rhs))
continue;
else
check_va_list_escapes (&si, lhs, rhs);
if (si.va_list_escapes)
{
if (dump_file && (dump_flags & TDF_DETAILS))
{
fputs ("va_list escapes in ", dump_file);
print_gimple_stmt (dump_file, phi, 0, dump_flags);
fputc ('\n', dump_file);
}
va_list_escapes = true;
}
}
}
for (unsigned j = 0; !va_list_escapes
&& j < gimple_phi_num_args (phi); ++j)
if ((!va_list_simple_ptr
|| TREE_CODE (gimple_phi_arg_def (phi, j)) != SSA_NAME)
&& walk_tree (gimple_phi_arg_def_ptr (phi, j),
find_va_list_reference, &wi, NULL))
{
if (dump_file && (dump_flags & TDF_DETAILS))
{
fputs ("va_list escapes in ", dump_file);
print_gimple_stmt (dump_file, phi, 0, dump_flags);
fputc ('\n', dump_file);
}
va_list_escapes = true;
}
}
for (gimple_stmt_iterator i = gsi_start_bb (bb);
!gsi_end_p (i) && !va_list_escapes;
gsi_next (&i))
{
gimple *stmt = gsi_stmt (i);
/* Don't look at __builtin_va_{start,end}, they are ok. */
if (is_gimple_call (stmt))
{
tree callee = gimple_call_fndecl (stmt);
if (callee
&& (fndecl_built_in_p (callee, BUILT_IN_VA_START)
|| fndecl_built_in_p (callee, BUILT_IN_VA_END)))
continue;
}
if (is_gimple_assign (stmt))
{
lhs = gimple_assign_lhs (stmt);
rhs = gimple_assign_rhs1 (stmt);
if (va_list_simple_ptr)
{
if (get_gimple_rhs_class (gimple_assign_rhs_code (stmt))
== GIMPLE_SINGLE_RHS)
{
/* Check for ap ={v} {}. */
if (TREE_CLOBBER_P (rhs))
continue;
/* Check for tem = ap. */
else if (va_list_ptr_read (&si, rhs, lhs))
continue;
/* Check for the last insn in:
tem1 = ap;
tem2 = tem1 + CST;
ap = tem2;
sequence. */
else if (va_list_ptr_write (&si, lhs, rhs))
continue;
}
if ((gimple_assign_rhs_code (stmt) == POINTER_PLUS_EXPR
&& TREE_CODE (gimple_assign_rhs2 (stmt)) == INTEGER_CST)
|| CONVERT_EXPR_CODE_P (gimple_assign_rhs_code (stmt))
|| (get_gimple_rhs_class (gimple_assign_rhs_code (stmt))
== GIMPLE_SINGLE_RHS))
check_va_list_escapes (&si, lhs, rhs);
}
else
{
if (get_gimple_rhs_class (gimple_assign_rhs_code (stmt))
== GIMPLE_SINGLE_RHS)
{
/* Check for ap ={v} {}. */
if (TREE_CLOBBER_P (rhs))
continue;
/* Check for ap[0].field = temp. */
else if (va_list_counter_struct_op (&si, lhs, rhs, true))
continue;
/* Check for temp = ap[0].field. */
else if (va_list_counter_struct_op (&si, rhs, lhs,
false))
continue;
}
/* Do any architecture specific checking. */
if (targetm.stdarg_optimize_hook
&& targetm.stdarg_optimize_hook (&si, stmt))
continue;
}
}
else if (is_gimple_debug (stmt))
continue;
/* All other uses of va_list are either va_copy (that is not handled
in this optimization), taking address of va_list variable or
passing va_list to other functions (in that case va_list might
escape the function and therefore va_start needs to set it up
fully), or some unexpected use of va_list. None of these should
happen in a gimplified VA_ARG_EXPR. */
if (si.va_list_escapes
|| walk_gimple_op (stmt, find_va_list_reference, &wi))
{
if (dump_file && (dump_flags & TDF_DETAILS))
{
fputs ("va_list escapes in ", dump_file);
print_gimple_stmt (dump_file, stmt, 0, dump_flags);
fputc ('\n', dump_file);
}
va_list_escapes = true;
}
}
if (va_list_escapes)
break;
}
if (! va_list_escapes
&& va_list_simple_ptr
&& ! bitmap_empty_p (si.va_list_escape_vars)
&& check_all_va_list_escapes (&si))
va_list_escapes = true;
finish:
if (va_list_escapes)
{
fun->va_list_gpr_size = VA_LIST_MAX_GPR_SIZE;
fun->va_list_fpr_size = VA_LIST_MAX_FPR_SIZE;
}
BITMAP_FREE (si.va_list_vars);
BITMAP_FREE (si.va_list_escape_vars);
free (si.offsets);
if (dump_file)
{
fprintf (dump_file, "%s: va_list escapes %d, needs to save ",
funcname, (int) va_list_escapes);
if (fun->va_list_gpr_size >= VA_LIST_MAX_GPR_SIZE)
fputs ("all", dump_file);
else
fprintf (dump_file, "%d", cfun->va_list_gpr_size);
fputs (" GPR units and ", dump_file);
if (fun->va_list_fpr_size >= VA_LIST_MAX_FPR_SIZE)
fputs ("all", dump_file);
else
fprintf (dump_file, "%d", cfun->va_list_fpr_size);
fputs (" FPR units.\n", dump_file);
}
}
/* Expand IFN_VA_ARGs in FUN. */
static void
expand_ifn_va_arg_1 (function *fun)
{
bool modified = false;
basic_block bb;
gimple_stmt_iterator i;
location_t saved_location;
FOR_EACH_BB_FN (bb, fun)
for (i = gsi_start_bb (bb); !gsi_end_p (i); gsi_next (&i))
{
gimple *stmt = gsi_stmt (i);
tree ap, aptype, expr, lhs, type;
gimple_seq pre = NULL, post = NULL;
if (!gimple_call_internal_p (stmt, IFN_VA_ARG))
continue;
modified = true;
type = TREE_TYPE (TREE_TYPE (gimple_call_arg (stmt, 1)));
ap = gimple_call_arg (stmt, 0);
aptype = TREE_TYPE (gimple_call_arg (stmt, 2));
gcc_assert (POINTER_TYPE_P (aptype));
/* Balanced out the &ap, usually added by build_va_arg. */
ap = build2 (MEM_REF, TREE_TYPE (aptype), ap,
build_int_cst (aptype, 0));
push_gimplify_context (false);
saved_location = input_location;
input_location = gimple_location (stmt);
/* Make it easier for the backends by protecting the valist argument
from multiple evaluations. */
gimplify_expr (&ap, &pre, &post, is_gimple_min_lval, fb_lvalue);
expr = targetm.gimplify_va_arg_expr (ap, type, &pre, &post);
lhs = gimple_call_lhs (stmt);
if (lhs != NULL_TREE)
{
unsigned int nargs = gimple_call_num_args (stmt);
gcc_assert (useless_type_conversion_p (TREE_TYPE (lhs), type));
if (nargs == 4)
{
/* We've transported the size of with WITH_SIZE_EXPR here as
the last argument of the internal fn call. Now reinstate
it. */
tree size = gimple_call_arg (stmt, nargs - 1);
expr = build2 (WITH_SIZE_EXPR, TREE_TYPE (expr), expr, size);
}
/* We use gimplify_assign here, rather than gimple_build_assign,
because gimple_assign knows how to deal with variable-sized
types. */
gimplify_assign (lhs, expr, &pre);
}
else
gimplify_and_add (expr, &pre);
input_location = saved_location;
pop_gimplify_context (NULL);
gimple_seq_add_seq (&pre, post);
update_modified_stmts (pre);
/* Add the sequence after IFN_VA_ARG. This splits the bb right
after IFN_VA_ARG, and adds the sequence in one or more new bbs
inbetween. */
gimple_find_sub_bbs (pre, &i);
/* Remove the IFN_VA_ARG gimple_call. It's the last stmt in the
bb. */
unlink_stmt_vdef (stmt);
release_ssa_name_fn (fun, gimple_vdef (stmt));
gsi_remove (&i, true);
gcc_assert (gsi_end_p (i));
/* We're walking here into the bbs which contain the expansion of
IFN_VA_ARG, and will not contain another IFN_VA_ARG that needs
expanding. We could try to skip walking these bbs, perhaps by
walking backwards over gimples and bbs. */
break;
}
if (!modified)
return;
free_dominance_info (CDI_DOMINATORS);
update_ssa (TODO_update_ssa);
}
/* Expand IFN_VA_ARGs in FUN, if necessary. */
static void
expand_ifn_va_arg (function *fun)
{
if ((fun->curr_properties & PROP_gimple_lva) == 0)
expand_ifn_va_arg_1 (fun);
if (flag_checking)
{
basic_block bb;
gimple_stmt_iterator i;
FOR_EACH_BB_FN (bb, fun)
for (i = gsi_start_bb (bb); !gsi_end_p (i); gsi_next (&i))
gcc_assert (!gimple_call_internal_p (gsi_stmt (i), IFN_VA_ARG));
}
}
namespace {
const pass_data pass_data_stdarg =
{
GIMPLE_PASS, /* type */
"stdarg", /* name */
OPTGROUP_NONE, /* optinfo_flags */
TV_NONE, /* tv_id */
( PROP_cfg | PROP_ssa ), /* properties_required */
PROP_gimple_lva, /* properties_provided */
0, /* properties_destroyed */
TODO_remove_unused_locals, /* todo_flags_start */
0, /* todo_flags_finish */
};
class pass_stdarg : public gimple_opt_pass
{
public:
pass_stdarg (gcc::context *ctxt)
: gimple_opt_pass (pass_data_stdarg, ctxt)
{}
/* opt_pass methods: */
virtual bool gate (function *)
{
/* Always run this pass, in order to expand va_arg internal_fns. We
also need to do that if fun->stdarg == 0, because a va_arg may also
occur in a function without varargs, f.i. if when passing a va_list to
another function. */
return true;
}
virtual unsigned int execute (function *);
}; // class pass_stdarg
unsigned int
pass_stdarg::execute (function *fun)
{
/* TODO: Postpone expand_ifn_va_arg till after
optimize_va_list_gpr_fpr_size. */
expand_ifn_va_arg (fun);
if (flag_stdarg_opt
/* This optimization is only for stdarg functions. */
&& fun->stdarg != 0)
optimize_va_list_gpr_fpr_size (fun);
return 0;
}
} // anon namespace
gimple_opt_pass *
make_pass_stdarg (gcc::context *ctxt)
{
return new pass_stdarg (ctxt);
}
namespace {
const pass_data pass_data_lower_vaarg =
{
GIMPLE_PASS, /* type */
"lower_vaarg", /* name */
OPTGROUP_NONE, /* optinfo_flags */
TV_NONE, /* tv_id */
( PROP_cfg | PROP_ssa ), /* properties_required */
PROP_gimple_lva, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
0, /* todo_flags_finish */
};
class pass_lower_vaarg : public gimple_opt_pass
{
public:
pass_lower_vaarg (gcc::context *ctxt)
: gimple_opt_pass (pass_data_lower_vaarg, ctxt)
{}
/* opt_pass methods: */
virtual bool gate (function *)
{
return (cfun->curr_properties & PROP_gimple_lva) == 0;
}
virtual unsigned int execute (function *);
}; // class pass_lower_vaarg
unsigned int
pass_lower_vaarg::execute (function *fun)
{
expand_ifn_va_arg (fun);
return 0;
}
} // anon namespace
gimple_opt_pass *
make_pass_lower_vaarg (gcc::context *ctxt)
{
return new pass_lower_vaarg (ctxt);
}