/* 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-2020 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 . */ #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; }