Mercurial > hg > CbC > CbC_gcc
diff gcc/config/riscv/riscv-sr.c @ 145:1830386684a0
gcc-9.2.0
author | anatofuz |
---|---|
date | Thu, 13 Feb 2020 11:34:05 +0900 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gcc/config/riscv/riscv-sr.c Thu Feb 13 11:34:05 2020 +0900 @@ -0,0 +1,465 @@ +/* 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/>. */ + +/* This file contains code aimed at optimizing function generated with the + use of '-msave-restore. The goal is to identify cases where the call + out to the save/restore routines are sub-optimal, and remove the calls + in this case. + + As GCC currently makes the choice between using or not using + save/restore early on (during the gimple expand pass) once we have + selected to use save/restore we are stuck with it. */ + +#define IN_TARGET_CODE 1 + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tm.h" +#include "rtl.h" +#include "function.h" +#include "memmodel.h" +#include "emit-rtl.h" +#include "target.h" +#include "basic-block.h" +#include "bitmap.h" +#include "df.h" +#include "tree.h" +#include "expr.h" +#include "cfg.h" + +/* This file should be included last. */ +#include "hard-reg-set.h" + +/* Look in the function prologue for a call to the save stub. Ensure that + the instruction is as we expect (see detail below) and if the + instruction matches return a pointer to it. Otherwise, return NULL. + + We expect the function prologue to look like this: + + (note NOTE_INSN_BASIC_BLOCK) + (insn (parallel [ + (unspec_volatile [ + (const_int 2 [0x2]) + ] UNSPECV_GPR_SAVE) + (clobber (reg:SI 5 t0)) + (clobber (reg:SI 6 t1))]) + (note NOTE_INSN_PROLOGUE_END) + + Between the NOTE_INSN_BASIC_BLOCK and the GPR_SAVE insn we might find + other notes of type NOTE_INSN_DELETED and/or NOTE_INSN_FUNCTION_BEG. + + The parameter BODY is updated to point to the first instruction after + the NOTE_INSN_PROLOGUE_END or will be updated to NULL if the prologue + end note was not found. */ + +static rtx_insn * +riscv_sr_match_prologue (rtx_insn **body) +{ + rtx_insn *insn, *bb_note; + *body = NULL; + + /* Find the prologue end note. */ + for (insn = get_insns (); insn != NULL; insn = NEXT_INSN (insn)) + if (NOTE_P (insn) && NOTE_KIND (insn) == NOTE_INSN_PROLOGUE_END) + { + *body = NEXT_INSN (insn); + break; + } + + /* If we don't have the prologue end note and at least one instruction + before it, then this function doesn't have the structure we expect. */ + if (insn == NULL + || PREV_INSN (insn) == NULL) + return NULL; + + /* The INSN is the end of prologue note, before this we expect to find + one real instruction which makes the prologue, and before that we + expect to find some number of notes for deleted instructions, the + beginning of the function, and finally a basicblock beginning. The + following loop checks that this assumption is true. */ + for (bb_note = PREV_INSN (PREV_INSN (insn)); + bb_note != NULL; + bb_note = PREV_INSN (bb_note)) + { + if (!NOTE_P (bb_note)) + return NULL; + if (NOTE_KIND (bb_note) == NOTE_INSN_BASIC_BLOCK) + break; + if (NOTE_KIND (bb_note) != NOTE_INSN_DELETED + && NOTE_KIND (bb_note) != NOTE_INSN_FUNCTION_BEG) + return NULL; + } + if (bb_note == NULL) + return NULL; + + /* Set INSN to point to the actual interesting prologue instruction. */ + insn = PREV_INSN (insn); + if (INSN_P (insn) + && INSN_CODE (insn) == CODE_FOR_gpr_save + /* Check this is a call to _riscv_save_0. */ + && GET_CODE (PATTERN (insn)) == PARALLEL + && GET_CODE (XVECEXP (PATTERN (insn), 0, 0)) == UNSPEC_VOLATILE + && (GET_CODE (XVECEXP (XVECEXP (PATTERN (insn), 0, 0), 0, 0)) + == CONST_INT) + && INTVAL (XVECEXP (XVECEXP (PATTERN (insn), 0, 0), 0, 0)) == 2) + return insn; + + return NULL; +} + +/* Find the first instruction in the epilogue of the current function, and + return a pointer to that instruction if, and only if, the epilogue has + the correct structure that would allow us to optimize out the call to + _riscv_restore_0. */ + +static rtx_insn * +riscv_sr_match_epilogue (void) +{ + /* Find the first instruction in the epilogue. */ + rtx_insn *insn, *start; + for (insn = get_insns (); insn != NULL; insn = NEXT_INSN (insn)) + if (NOTE_P (insn) && NOTE_KIND (insn) == NOTE_INSN_EPILOGUE_BEG) + { + insn = NEXT_INSN (insn); + break; + } + if (insn == NULL) + return NULL; + + /* At this point INSN is the first instruction in the epilogue. A + standard epilogue (of the form we expect to handle) consists of the + following instructions: + + 1. A stack_tiesi or stack_tiedi (for RV32 and RV64 respectively), + + 2. An optional use instruction for the register holding the return + value. This will be missing in functions with no return value, + + 3. A gpr_restore instruction, and + + 4. A jump instruction of type gpr_restore_return. */ + start = insn; + if (INSN_CODE (insn) != CODE_FOR_stack_tiesi + && INSN_CODE (insn) != CODE_FOR_stack_tiedi) + return NULL; + + insn = NEXT_INSN (insn); + if (INSN_P (insn) && GET_CODE (PATTERN (insn)) == USE) + insn = NEXT_INSN (insn); + + if (!INSN_P (insn) || INSN_CODE (insn) != CODE_FOR_gpr_restore) + return NULL; + + insn = NEXT_INSN (insn); + if (!INSN_P (insn) || INSN_CODE (insn) != CODE_FOR_gpr_restore_return) + return NULL; + + return start; +} + +/* Helper for riscv_remove_unneeded_save_restore_calls. If we match the + prologue instructions but not the epilogue then we might have the case + where the epilogue has been optimized out due to a call to a no-return + function. In this case we might be able to remove the prologue too - + that's what this function does. PROLOGUE is the matched prolgoue + instruction, by the time this function returns the progloue instruction + may have been removed. */ + +static void +check_for_no_return_call (rtx_insn *prologue) +{ + /* Check to see if we have the following pattern: + + PROLOGUE instruction + NOTE_INSN_PROLOGUE_END + A no-return call instruction + + If we do, then we can remove the prologue instruction safely. Remember + that we've already confirmed by this point that the prologue is a call + to riscv_save_0. */ + + if (dump_file) + fprintf (dump_file, + "Prologue matched, checking for no-return epilogue.\n"); + + rtx_insn *tmp = NEXT_INSN (prologue); + if (!NOTE_P (tmp) || NOTE_KIND (tmp) != NOTE_INSN_PROLOGUE_END) + return; + + /* Skip any extra notes in here, they're most likely just debug. */ + do + { + tmp = NEXT_INSN (tmp); + } + while (tmp != NULL && NOTE_P (tmp)); + + if (tmp == NULL || !INSN_P (tmp)) + return; + + bool noreturn_p = find_reg_note (tmp, REG_NORETURN, NULL_RTX) != NULL_RTX; + if (!CALL_P (tmp) || !noreturn_p) + return; + + if (dump_file) + fprintf (dump_file, + "Prologue call to riscv_save_0 followed by noreturn call, " + "removing prologue.\n"); + remove_insn (prologue); +} + +/* Entry point called from riscv_reorg to remove some unneeded calls to + the save and restore stubs. This should only be called when + -msave-restore is in use. + + We identify some simple cases where the function looks like this: + + call t0,__riscv_save_0 + <other-code> + call foo + tail __riscv_restore_0 + + And transform it into something like this: + + <other-code> + tail foo + + In the above examples, what can appear in <other-code> is pretty + restricted; only caller saved registers can be touched, this prevents + any additional calls (as they would write to 'ra'). */ + +void +riscv_remove_unneeded_save_restore_calls (void) +{ + /* Will point to the first instruction of the function body, after the + prologue end note. */ + rtx_insn *body = NULL; + + /* Should only be called with -msave-restore is in use. */ + gcc_assert (TARGET_SAVE_RESTORE); + + /* Match the expected prologue and epilogue patterns. If either of these + fail to match then we abandon our attempt to optimize this function. */ + rtx_insn *prologue_matched = riscv_sr_match_prologue (&body); + if (prologue_matched == NULL || body == NULL) + return; + + rtx_insn *epilogue_matched = riscv_sr_match_epilogue (); + if (epilogue_matched == NULL) + { + check_for_no_return_call (prologue_matched); + return; + } + + if (dump_file) + fprintf (dump_file, + "Could be a candidate for save/restore removal\n"); + + /* We want to check which registers this function uses. */ + df_analyze (); + + int call_count = 0; + bool good_use = true; + int epilogue_count = 0; + + /* Now examine all of the instructions that make up this function, we're + looking for call instructions and also double checking register usage + while we're at it (see comments below). */ + basic_block bb; + FOR_EACH_BB_FN (bb, cfun) + { + rtx_insn *insn; + + FOR_BB_INSNS (bb, insn) + { + if (dump_file) + fprintf (dump_file, + "Block %d, Insn %d\n", bb->index, INSN_UID (insn)); + + /* If we scan the epilogue we will fall foul of our register + usage check below (due to it's use of the return address), so + once we spot we're at the epilogue, just skip the rest of this + block. Scanning the prologue instructions again (if they + match the expected pattern) is harmless. */ + if (NOTE_P (insn) + && NOTE_KIND (insn) == NOTE_INSN_EPILOGUE_BEG) + { + ++epilogue_count; + break; + } + + if (!INSN_P (insn)) + continue; + + if (CALL_P (insn)) + ++call_count; + else + { + df_ref use; + + FOR_EACH_INSN_USE (use, insn) + { + /* If the function makes use of any registers that are + callee saved then we should be saving them in this + function, which would suggest that a call to the save + and restore functions is required. This would seem to + indicate that something has gone wrong above, as we + should only get here if we are saving zero registers. + + The one exception to this rule is the return address + register used within a call instruction. We can + optimize a single call within a function (by making it + a tail call), so we skip call instructions here. */ + if (!call_used_regs[DF_REF_REGNO (use)]) + { + if (dump_file) + fprintf (dump_file, + "Found unsupported use of callee saved " + "register in instruction %d\n", + INSN_UID (insn)); + good_use = false; + break; + } + } + if (!good_use) + break; + } + } + } + + /* If we used any registers that would indicate a need for a call to a + save/restore stub then don't optimize. */ + if (!good_use) + return; + + /* If this function has multiple epilogues, then for now we don't try to + optimize it. */ + if (epilogue_count != 1) + return; + + /* We can only optimize functions containing a single call, any more + would require us to add instructions to store the return address on + the stack (and restore it before we return). We could do this in the + future, but for now we don't. A single call can be transformed into + a tail call reasonably easily. */ + if (call_count > 1) + { + if (dump_file) + fprintf (dump_file, + "Found too many call instructions\n"); + return; + } + + rtx_insn *epilogue_begin_note = PREV_INSN (epilogue_matched); + gcc_assert (NOTE_P (epilogue_begin_note) + && NOTE_KIND (epilogue_begin_note) == NOTE_INSN_EPILOGUE_BEG); + + df_finish_pass (false); + + /* Find the first instruction before the function epilogue. */ + rtx_insn *insn_before_epilogue; + for (insn_before_epilogue = PREV_INSN (epilogue_begin_note); + NOTE_P (insn_before_epilogue); + insn_before_epilogue = PREV_INSN (insn_before_epilogue)) + ; + + /* Leaf functions will not generate calls to the save/restore stubs, so + there's no need for this optimization there. We know this function + has no more than 1 call (checked above). To convert this single call + into a tail call we rely on the call being the last thing before the + epilogue. */ + if (GET_CODE (insn_before_epilogue) != CALL_INSN) + return; + + /* The last instruction in this block, just before the epilogue is a + call. We can potentially change this call into a tail call. */ + rtx_insn *call = insn_before_epilogue; + + /* Transform call in insn to a sibcall, this will only be done if the + last thing in the function is a call. */ + rtx callpat = PATTERN (call); + gcc_assert (GET_CODE (callpat) == PARALLEL); + + /* Extract from CALLPAT the information we need to build the sibcall. */ + rtx target_call = NULL; + rtx tmp_rtx = XVECEXP (callpat, 0, 0); + rtx set_target = NULL; + switch (GET_CODE (tmp_rtx)) + { + case CALL: + target_call = tmp_rtx; + break; + + case SET: + { + set_target = XEXP (tmp_rtx, 0); + tmp_rtx = XEXP (tmp_rtx, 1); + if (GET_CODE (tmp_rtx) != CALL) + return; + target_call = tmp_rtx; + break; + } + + default: + return; + } + + rtx target_mem = XEXP (target_call, 0); + if (GET_CODE (target_mem) != MEM) + return; + + rtx target = XEXP (target_mem, 0); + if (GET_CODE (target) != SYMBOL_REF && GET_CODE (target) != REG) + return; + + /* The sibcall instructions can only use a specific subset of + registers, we're about to (possibly) move a call through a + register from the function body and make it a sibcall. If we're + not using an appropriate register then we can't make this change. + + Maybe in some future iteration we could actually scan the + function, find a suitable sibcall register, and switch over the + registers. But we don't do that yet. */ + if (GET_CODE (target) == REG + && !SIBCALL_REG_P (REGNO (target))) + return; + + rtx sibcall = NULL; + if (set_target != NULL) + sibcall + = gen_sibcall_value_internal (set_target, target, const0_rtx); + else + sibcall = gen_sibcall_internal (target, const0_rtx); + + rtx_insn *before_call = PREV_INSN (call); + remove_insn (call); + rtx_insn *insn = emit_call_insn_after_setloc (sibcall, before_call, + INSN_LOCATION (call)); + REG_NOTES (insn) = REG_NOTES (call); + SIBLING_CALL_P (insn) = 1; + + /* Now update the prologue and epilogue to take account of the + changes within the function body. */ + remove_insn (prologue_matched); + remove_insn (NEXT_INSN (NEXT_INSN (NEXT_INSN (epilogue_matched)))); + remove_insn (NEXT_INSN (NEXT_INSN (epilogue_matched))); + remove_insn (NEXT_INSN (epilogue_matched)); + remove_insn (epilogue_matched); + + if (dump_file) + fprintf (dump_file, + "Save/restore successfully removed\n"); +}