diff options
Diffstat (limited to 'gcc/config/mips/frame-header-opt.cc')
-rw-r--r-- | gcc/config/mips/frame-header-opt.cc | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/gcc/config/mips/frame-header-opt.cc b/gcc/config/mips/frame-header-opt.cc new file mode 100644 index 0000000..a29b823 --- /dev/null +++ b/gcc/config/mips/frame-header-opt.cc @@ -0,0 +1,292 @@ +/* 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-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/>. */ + + +#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; +} |