blob: 06fd689bf73c1cf00e2f97b4a3e01346980db3b8 [file] [log] [blame]
/* Analyze functions to determine if callers need to allocate a frame header
on the stack. The frame header is used by callees to save their arguments.
This optimization is specific to TARGET_OLDABI targets. For TARGET_NEWABI
targets, if a frame header is required, it is allocated by the callee.
Copyright (C) 2015-2021 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/>. */
#define IN_TARGET_CODE 1
#include "config.h"
#include "system.h"
#include "context.h"
#include "coretypes.h"
#include "backend.h"
#include "tree.h"
#include "tree-core.h"
#include "tree-pass.h"
#include "target.h"
#include "target-globals.h"
#include "profile-count.h"
#include "cgraph.h"
#include "function.h"
#include "basic-block.h"
#include "gimple.h"
#include "gimple-iterator.h"
#include "gimple-walk.h"
static unsigned int frame_header_opt (void);
namespace {
const pass_data pass_data_ipa_frame_header_opt =
{
IPA_PASS, /* type */
"frame-header-opt", /* name */
OPTGROUP_NONE, /* optinfo_flags */
TV_CGRAPHOPT, /* tv_id */
0, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
0, /* todo_flags_finish */
};
class pass_ipa_frame_header_opt : public ipa_opt_pass_d
{
public:
pass_ipa_frame_header_opt (gcc::context *ctxt)
: ipa_opt_pass_d (pass_data_ipa_frame_header_opt, ctxt,
NULL, /* generate_summary */
NULL, /* write_summary */
NULL, /* read_summary */
NULL, /* write_optimization_summary */
NULL, /* read_optimization_summary */
NULL, /* stmt_fixup */
0, /* function_transform_todo_flags_start */
NULL, /* function_transform */
NULL) /* variable_transform */
{}
/* opt_pass methods: */
virtual bool gate (function *)
{
/* This optimization has no affect if TARGET_NEWABI. If optimize
is not at least 1 then the data needed for the optimization is
not available and nothing will be done anyway. */
return TARGET_OLDABI && flag_frame_header_optimization && optimize > 0;
}
virtual unsigned int execute (function *) { return frame_header_opt (); }
}; // class pass_ipa_frame_header_opt
} // anon namespace
static ipa_opt_pass_d *
make_pass_ipa_frame_header_opt (gcc::context *ctxt)
{
return new pass_ipa_frame_header_opt (ctxt);
}
void
mips_register_frame_header_opt (void)
{
opt_pass *p = make_pass_ipa_frame_header_opt (g);
struct register_pass_info f = { p, "comdats", 1, PASS_POS_INSERT_AFTER };
register_pass (&f);
}
/* Return true if it is certain that this is a leaf function. False if it is
not a leaf function or if it is impossible to tell. */
static bool
is_leaf_function (function *fn)
{
basic_block bb;
gimple_stmt_iterator gsi;
/* If we do not have a cfg for this function be conservative and assume
it is not a leaf function. */
if (fn->cfg == NULL)
return false;
FOR_EACH_BB_FN (bb, fn)
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
if (is_gimple_call (gsi_stmt (gsi)))
return false;
return true;
}
/* Return true if this function has inline assembly code or if we cannot
be certain that it does not. False if we know that there is no inline
assembly. */
static bool
has_inlined_assembly (function *fn)
{
basic_block bb;
gimple_stmt_iterator gsi;
/* If we do not have a cfg for this function be conservative and assume
it is may have inline assembly. */
if (fn->cfg == NULL)
return true;
FOR_EACH_BB_FN (bb, fn)
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
if (gimple_code (gsi_stmt (gsi)) == GIMPLE_ASM)
return true;
return false;
}
/* Return true if this function will use the stack space allocated by its
caller or if we cannot determine for certain that it does not. */
static bool
needs_frame_header_p (function *fn)
{
tree t;
if (fn->decl == NULL)
return true;
if (fn->stdarg)
return true;
for (t = DECL_ARGUMENTS (fn->decl); t; t = TREE_CHAIN (t))
{
if (!use_register_for_decl (t))
return true;
/* Some 64-bit types may get copied to general registers using the frame
header, see mips_output_64bit_xfer. Checking for SImode only may be
overly restrictive but it is guaranteed to be safe. */
if (DECL_MODE (t) != SImode)
return true;
}
return false;
}
/* Return true if the argument stack space allocated by function FN is used.
Return false if the space is needed or if the need for the space cannot
be determined. */
static bool
callees_functions_use_frame_header (function *fn)
{
basic_block bb;
gimple_stmt_iterator gsi;
gimple *stmt;
tree called_fn_tree;
function *called_fn;
if (fn->cfg == NULL)
return true;
FOR_EACH_BB_FN (bb, fn)
{
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
{
stmt = gsi_stmt (gsi);
if (is_gimple_call (stmt))
{
called_fn_tree = gimple_call_fndecl (stmt);
if (called_fn_tree != NULL)
{
called_fn = DECL_STRUCT_FUNCTION (called_fn_tree);
if (called_fn == NULL
|| DECL_WEAK (called_fn_tree)
|| has_inlined_assembly (called_fn)
|| !is_leaf_function (called_fn)
|| !called_fn->machine->does_not_use_frame_header)
return true;
}
else
return true;
}
}
}
return false;
}
/* Set the callers_may_not_allocate_frame flag for any function which
function FN calls because FN may not allocate a frame header. */
static void
set_callers_may_not_allocate_frame (function *fn)
{
basic_block bb;
gimple_stmt_iterator gsi;
gimple *stmt;
tree called_fn_tree;
function *called_fn;
if (fn->cfg == NULL)
return;
FOR_EACH_BB_FN (bb, fn)
{
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
{
stmt = gsi_stmt (gsi);
if (is_gimple_call (stmt))
{
called_fn_tree = gimple_call_fndecl (stmt);
if (called_fn_tree != NULL)
{
called_fn = DECL_STRUCT_FUNCTION (called_fn_tree);
if (called_fn != NULL)
called_fn->machine->callers_may_not_allocate_frame = true;
}
}
}
}
return;
}
/* Scan each function to determine those that need its frame headers. Perform
a second scan to determine if the allocation can be skipped because none of
their callees require the frame header. */
static unsigned int
frame_header_opt ()
{
struct cgraph_node *node;
function *fn;
FOR_EACH_DEFINED_FUNCTION (node)
{
fn = node->get_fun ();
if (fn != NULL)
fn->machine->does_not_use_frame_header = !needs_frame_header_p (fn);
}
FOR_EACH_DEFINED_FUNCTION (node)
{
fn = node->get_fun ();
if (fn != NULL)
fn->machine->optimize_call_stack
= !callees_functions_use_frame_header (fn) && !is_leaf_function (fn);
}
FOR_EACH_DEFINED_FUNCTION (node)
{
fn = node->get_fun ();
if (fn != NULL && fn->machine->optimize_call_stack)
set_callers_may_not_allocate_frame (fn);
}
return 0;
}