From patchwork Mon Aug 19 19:15:43 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Burgess X-Patchwork-Id: 1149520 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=gcc.gnu.org (client-ip=209.132.180.131; helo=sourceware.org; envelope-from=gcc-patches-return-507292-incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=embecosm.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=gcc.gnu.org header.i=@gcc.gnu.org header.b="ZJ6y7Try"; dkim=pass (2048-bit key; unprotected) header.d=embecosm.com header.i=@embecosm.com header.b="XrHht7oI"; dkim-atps=neutral Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 46C3Z02Fkfz9sN1 for ; Tue, 20 Aug 2019 05:16:24 +1000 (AEST) DomainKey-Signature: a=rsa-sha1; c=nofws; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender:from :to:cc:subject:date:message-id:in-reply-to:references :in-reply-to:references; q=dns; s=default; b=ckWS1Rf3q51OWU5zFtc sS4aqm1xt5TEomuid2wPi51J7o1AuNDHp9CwRc+cl3peaVExc7hwdvEN6AtNmQLp F2Rt19aXBD9M5Hlu82/CNCE3w0RTFW6Q0arn2r5qtleEiPXYMC/IeLnBhtnG/Irw BPU/14eTSIOtvFB33Kn0/+B0= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender:from :to:cc:subject:date:message-id:in-reply-to:references :in-reply-to:references; s=default; bh=9C2EDPkKmlX0SSLo3Sz5u84g7 IA=; b=ZJ6y7TryB4Vdaqx4dc2O/8QfoPMaa1i7L9fjdL+Bl9evoMl4fXY7IQmWT i64JIZ7n4mQcLBWTuD86x09Hpjgps6CbSrzJznjcNCYhUnLov6M4NUnsz/0Y0m25 /zyEOyunkaYEKCYm4J9b2O7vL8CcBf7uGWQZFCJXi/EITt7gOM= Received: (qmail 81424 invoked by alias); 19 Aug 2019 19:15:59 -0000 Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org Received: (qmail 81371 invoked by uid 89); 19 Aug 2019 19:15:59 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-22.9 required=5.0 tests=AWL, BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_NUMSUBJECT, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_PASS, UNSUBSCRIBE_BODY autolearn=ham version=3.3.1 spammy= X-HELO: mail-wm1-f68.google.com Received: from mail-wm1-f68.google.com (HELO mail-wm1-f68.google.com) (209.85.128.68) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Mon, 19 Aug 2019 19:15:53 +0000 Received: by mail-wm1-f68.google.com with SMTP id v19so502173wmj.5 for ; Mon, 19 Aug 2019 12:15:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=embecosm.com; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :in-reply-to:references; bh=b2k6PeshTSdhYyW4/vSJgd13FbG3/jTLDu1cyDm65YM=; b=XrHht7oI6gxQ1Vvc/DAniOVZstp4643l6Qc5rbHnwA5aEX3ZC67HQZGjhxQtSo39lH D5enE1U+U5PHcZ1jMEK0FuDc7RCMUq0uWvRm6LkWptv6ZkxjNPB2/dsafqUGvRp6gAC2 pHFptPQVIIGeeLgF8UpbCTC9IP7/oR+AHhI0ZmzpFMZ6Ic91rfR7FiVElAVp5VY84lyf zCO0S3g9Cdhg0OyCmmid9SWlafk2QlhmWHqZi8y9RKA8iOrsuerHRVJKL7d/y1DBENCQ ID4qHBLP/GL3yOOZ+5gyMUnEUSXAJrCjhaFQjCqX1DqRTMqW1UqwaX+Ok04GlQk1setn I3Qg== Received: from localhost (host86-128-12-79.range86-128.btcentralplus.com. [86.128.12.79]) by smtp.gmail.com with ESMTPSA id p9sm13957851wru.61.2019.08.19.12.15.49 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Mon, 19 Aug 2019 12:15:49 -0700 (PDT) From: Andrew Burgess To: gcc-patches@gcc.gnu.org Cc: Jim Wilson , Andrew Burgess Subject: [PATCH 2/2] gcc/riscv: Add a mechanism to remove some calls to _riscv_save_0 Date: Mon, 19 Aug 2019 20:15:43 +0100 Message-Id: In-Reply-To: References: In-Reply-To: References: X-IsSubscribed: yes When using the -msave-restore flag we end up with calls to _riscv_save_0 and _riscv_restore_0. These functions adjust the stack and save or restore the return address. Due to grouping multiple save/restore stub functions together the save/restore 0 calls actually save s0, s1, s2, and the return address, but only the return address actually matters. Leaf functions don't call the save/restore stubs, so whenever we do see a call to the save/restore stubs, the store of the return address is required. If we look in gcc/config/riscv/riscv.c at the function riscv_expand_prologue and riscv_expand_epilogue we can see that it would be reasonably easy to adjust these functions to avoid the calls to the save/restore stubs for those cases where we are about to call _riscv_save_0 and _riscv_restore_0, however, the actual code size saving this would give is debatable, with linker relaxation, the calls to save/restore are often just 4-bytes, and can sometimes even be 2-bytes, while leaving the stack adjust and return address save inline is always going to be 4-bytes. The interesting case is when we call _riscv_save_0 and _riscv_restore_0, and also have a frame that would (without save/restore) have resulted in a tail call. In this case if we could remove the save/restore calls, and restore the tail call then we would get a real size saving. The problem is that the choice of generating a tail call or not is done during the gimple expand pass, at which point we don't know how many registers we need to save (or restore). The solution presented in this patch offers a partial solution to this problem. By using the TARGET_MACHINE_DEPENDENT_REORG pass to implement a very limited pattern matching we identify functions that call _riscv_save_0 and _riscv_restore_0, and which could be converted to make use of a tail call. These functions are then converted to the non save/restore tail call form. This should result in a code size reduction when compiling with -Os and with the -msave-restore flag. gcc/ChangeLog: * config.gcc: Add riscv-sr.o to extra_objs for riscv. * config/riscv/riscv-sr.c: New file. * config/riscv/riscv.c (riscv_reorg): New function. (TARGET_MACHINE_DEPENDENT_REORG): Define. * config/riscv/riscv.h (SIBCALL_REG_P): Define. (CALLEE_SAVED_REG_P): Define. (riscv_remove_unneeded_save_restore_calls): Declare. * config/riscv/t-riscv (riscv-sr.o): New build rule. gcc/testsuite/ChangeLog: * gcc.target/riscv/save-restore-2.c: New file. * gcc.target/riscv/save-restore-3.c: New file. * gcc.target/riscv/save-restore-4.c: New file. * gcc.target/riscv/save-restore-5.c: New file. * gcc.target/riscv/save-restore-6.c: New file. * gcc.target/riscv/save-restore-7.c: New file. * gcc.target/riscv/save-restore-8.c: New file. --- gcc/ChangeLog | 11 + gcc/config.gcc | 2 +- gcc/config/riscv/riscv-sr.c | 375 ++++++++++++++++++++++++ gcc/config/riscv/riscv.c | 13 + gcc/config/riscv/riscv.h | 20 ++ gcc/config/riscv/t-riscv | 5 + gcc/testsuite/ChangeLog | 10 + gcc/testsuite/gcc.target/riscv/save-restore-2.c | 22 ++ gcc/testsuite/gcc.target/riscv/save-restore-3.c | 16 + gcc/testsuite/gcc.target/riscv/save-restore-4.c | 27 ++ gcc/testsuite/gcc.target/riscv/save-restore-5.c | 9 + gcc/testsuite/gcc.target/riscv/save-restore-6.c | 16 + gcc/testsuite/gcc.target/riscv/save-restore-7.c | 30 ++ gcc/testsuite/gcc.target/riscv/save-restore-8.c | 12 + 14 files changed, 567 insertions(+), 1 deletion(-) create mode 100644 gcc/config/riscv/riscv-sr.c create mode 100644 gcc/testsuite/gcc.target/riscv/save-restore-2.c create mode 100644 gcc/testsuite/gcc.target/riscv/save-restore-3.c create mode 100644 gcc/testsuite/gcc.target/riscv/save-restore-4.c create mode 100644 gcc/testsuite/gcc.target/riscv/save-restore-5.c create mode 100644 gcc/testsuite/gcc.target/riscv/save-restore-6.c create mode 100644 gcc/testsuite/gcc.target/riscv/save-restore-7.c create mode 100644 gcc/testsuite/gcc.target/riscv/save-restore-8.c diff --git a/gcc/config.gcc b/gcc/config.gcc index 40cbc52dc994..ac02d1835372 100644 --- a/gcc/config.gcc +++ b/gcc/config.gcc @@ -520,7 +520,7 @@ pru-*-*) ;; riscv*) cpu_type=riscv - extra_objs="riscv-builtins.o riscv-c.o" + extra_objs="riscv-builtins.o riscv-c.o riscv-sr.o" d_target_objs="riscv-d.o" ;; rs6000*-*-*) diff --git a/gcc/config/riscv/riscv-sr.c b/gcc/config/riscv/riscv-sr.c new file mode 100644 index 000000000000..7d12c0d82111 --- /dev/null +++ b/gcc/config/riscv/riscv-sr.c @@ -0,0 +1,375 @@ +/* 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 +. */ + +/* This file contains code aimed at optimising 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. */ + +#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" + +/* 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. */ + +static rtx_insn * +riscv_sr_match_prologue (void) +{ + rtx_insn *insn, *bb_note; + bool seen_first_insn = false; + + /* 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) + 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 sone 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) + && XINT (XVECEXP (XVECEXP (PATTERN (insn), 0, 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 optimise 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; +} + +/* 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 + + call foo + tail __riscv_restore_0 + + And transform it into something like this: + + + tail foo + + In the above examples, what can appear in 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) +{ + /* 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 optimise this function. */ + rtx_insn *prologue_matched = riscv_sr_match_prologue(); + if (prologue_matched == NULL) + return; + + rtx_insn *epilogue_matched = riscv_sr_match_epilogue (); + if (epilogue_matched == NULL) + 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 (); + + /* This finds the prologue end note. */ + rtx_insn *body; + for (body = get_insns (); body != NULL; body = NEXT_INSN (body)) + if (NOTE_P (body) && NOTE_KIND (body) == NOTE_INSN_PROLOGUE_END) + { + body = NEXT_INSN (body); + break; + } + + int call_count = 0; + bool good_use = true; + df_ref use; + + /* Now examine all of the instructions up to the epilogue begin note. */ + rtx_insn *insn; + for (insn = body; + !NOTE_P (insn) || NOTE_KIND (insn) != NOTE_INSN_EPILOGUE_BEG; + insn = NEXT_INSN (insn)) + { + if (!INSN_P (insn)) + continue; + + if (CALL_P (insn)) + ++call_count; + else + { + 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 optimise + a single call within a function (by making it a tail + call), so we skip call instructions here. */ + if (CALLEE_SAVED_REG_P (DF_REF_REGNO (use))) + { + 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 optimise. */ + if (!good_use) + { + if (dump_file) + fprintf (dump_file, + "Found unsupported use of callee saved register\n"); + return; + } + + /* We can only optimise 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 = insn; + 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 optimisation 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); + insn = emit_call_insn_after (sibcall, before_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"); +} diff --git a/gcc/config/riscv/riscv.c b/gcc/config/riscv/riscv.c index c12b26f0dc40..daa2648ebb09 100644 --- a/gcc/config/riscv/riscv.c +++ b/gcc/config/riscv/riscv.c @@ -4996,6 +4996,16 @@ riscv_promote_function_mode (const_tree type ATTRIBUTE_UNUSED, return mode; } +/* Implement TARGET_MACHINE_DEPENDENT_REORG. */ + +static void +riscv_reorg (void) +{ + /* Do nothing unless we have -msave-restore */ + if (TARGET_SAVE_RESTORE) + riscv_remove_unneeded_save_restore_calls (); +} + /* Initialize the GCC target structure. */ #undef TARGET_ASM_ALIGNED_HI_OP #define TARGET_ASM_ALIGNED_HI_OP "\t.half\t" @@ -5170,6 +5180,9 @@ riscv_promote_function_mode (const_tree type ATTRIBUTE_UNUSED, #undef TARGET_CUSTOM_FUNCTION_DESCRIPTORS #define TARGET_CUSTOM_FUNCTION_DESCRIPTORS 1 +#undef TARGET_MACHINE_DEPENDENT_REORG +#define TARGET_MACHINE_DEPENDENT_REORG riscv_reorg + struct gcc_target targetm = TARGET_INITIALIZER; #include "gt-riscv.h" diff --git a/gcc/config/riscv/riscv.h b/gcc/config/riscv/riscv.h index bb8240bb849a..54e10954e3fe 100644 --- a/gcc/config/riscv/riscv.h +++ b/gcc/config/riscv/riscv.h @@ -289,6 +289,22 @@ along with GCC; see the file COPYING3. If not see #define FP_REG_P(REGNO) \ ((unsigned int) ((int) (REGNO) - FP_REG_FIRST) < FP_REG_NUM) +/* True when REGNO is in SIBCALL_REGS set. */ +#define SIBCALL_REG_P(REGNO) \ + (GP_REG_P(REGNO) && (((REGNO) >= 5 && (REGNO) <= 7) \ + || ((REGNO) >= 10 && (REGNO) <= 17) \ + || ((REGNO) >= 28 && (REGNO) <= 31))) + +/* True if REGNO is a register saved by the callee. */ +#define CALLEE_SAVED_REG_P(REGNO) \ + ((GP_REG_P (REGNO) \ + && ((REGNO) == 2 \ + || ((REGNO) >= 8 && (REGNO) <= 9) \ + || ((REGNO) >= 18 && (REGNO) <= 27))) \ + || (FP_REG_P (REGNO) \ + && (((REGNO) >= 40 && (REGNO) <= 41) \ + || ((REGNO) >= 50 && (REGNO) <= 59)))) + #define FP_REG_RTX_P(X) (REG_P (X) && FP_REG_P (REGNO (X))) /* Use s0 as the frame pointer if it is so requested. */ @@ -918,4 +934,8 @@ extern unsigned riscv_stack_boundary; #define SWSP_REACH (4LL << C_SxSP_BITS) #define SDSP_REACH (8LL << C_SxSP_BITS) +/* Called from RISCV_REORG, this is defined in riscv-sr.c. */ + +extern void riscv_remove_unneeded_save_restore_calls (void); + #endif /* ! GCC_RISCV_H */ diff --git a/gcc/config/riscv/t-riscv b/gcc/config/riscv/t-riscv index ece3a75d512e..5ecb3c160a61 100644 --- a/gcc/config/riscv/t-riscv +++ b/gcc/config/riscv/t-riscv @@ -5,6 +5,11 @@ riscv-builtins.o: $(srcdir)/config/riscv/riscv-builtins.c $(CONFIG_H) \ $(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) \ $(srcdir)/config/riscv/riscv-builtins.c +riscv-sr.o: $(srcdir)/config/riscv/riscv-sr.c $(CONFIG_H) \ + $(SYSTEM_H) + $(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) \ + $(srcdir)/config/riscv/riscv-sr.c + riscv-c.o: $(srcdir)/config/riscv/riscv-c.c $(CONFIG_H) $(SYSTEM_H) \ coretypes.h $(TM_H) $(TREE_H) output.h $(C_COMMON_H) $(TARGET_H) $(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) \ diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-2.c b/gcc/testsuite/gcc.target/riscv/save-restore-2.c new file mode 100644 index 000000000000..204bf67b66e5 --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/save-restore-2.c @@ -0,0 +1,22 @@ +/* { dg-options "-Os -msave-restore" } */ + +/* With -msave-restore in use it should not be possible to remove the calls + to the save and restore stubs in this case (in current GCC). */ + +extern void fn2 (); + +volatile int a = 0; + +int +fn1 () +{ + fn2 (); + + while (a) + ; + + return 0; +} + +/* { dg-final { scan-assembler "call\[ \t\]*t0,__riscv_save_0" } } */ +/* { dg-final { scan-assembler "tail\[ \t\]*__riscv_restore_0" } } */ diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-3.c b/gcc/testsuite/gcc.target/riscv/save-restore-3.c new file mode 100644 index 000000000000..6bf9fb014d6b --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/save-restore-3.c @@ -0,0 +1,16 @@ +/* { dg-options "-Os -msave-restore" } */ + +/* With -msave-restore in use GCC should be able to remove the calls to the + save and restore stubs in this case, replacing them with a tail call to + foo. */ + +extern int foo (); + +int bar () +{ + return foo (); +} + +/* { dg-final { scan-assembler-not "call\[ \t\]*t0,__riscv_save_0" } } */ +/* { dg-final { scan-assembler-not "tail\[ \t\]*__riscv_restore_0" } } */ +/* { dg-final { scan-assembler "tail\[ \t\]*foo" } } */ diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-4.c b/gcc/testsuite/gcc.target/riscv/save-restore-4.c new file mode 100644 index 000000000000..c1c10c604a1b --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/save-restore-4.c @@ -0,0 +1,27 @@ +/* { dg-options "-Os -msave-restore" } */ + +/* This test covers a case where we can't (currently) remove the calls to + the save/restore stubs. The cast of the return value from BAR requires + a sign extension between the call to BAR, and the return from FOO, this + currently prevents the removal of the save/restore calls. */ + +typedef enum SomeType + { + A = 0, + B = 1, + C = 2, + D = 3 + } + SomeType; + +typedef unsigned int u_32; + +extern u_32 bar (u_32 arg); + +SomeType foo (u_32 arg) +{ + return (SomeType) bar (arg); +} + +/* { dg-final { scan-assembler "call\[ \t\]*t0,__riscv_save_0" } } */ +/* { dg-final { scan-assembler "tail\[ \t\]*__riscv_restore_0" } } */ diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-5.c b/gcc/testsuite/gcc.target/riscv/save-restore-5.c new file mode 100644 index 000000000000..fe0ffdcd5044 --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/save-restore-5.c @@ -0,0 +1,9 @@ +typedef int (*FPTR) (void); +FPTR a; + +int +func () +{ + int b = a (); + return b; +} diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-6.c b/gcc/testsuite/gcc.target/riscv/save-restore-6.c new file mode 100644 index 000000000000..c5d25ad006fe --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/save-restore-6.c @@ -0,0 +1,16 @@ +/* { dg-options "-Os -msave-restore" } */ + +/* With -msave-restore in use GCC should be able to remove the calls to the + save and restore stubs in this case, replacing them with a tail call to + foo. */ + +extern void other_func (); + +void func () +{ + other_func (); +} + +/* { dg-final { scan-assembler-not "call\[ \t\]*t0,__riscv_save_0" } } */ +/* { dg-final { scan-assembler-not "tail\[ \t\]*__riscv_restore_0" } } */ +/* { dg-final { scan-assembler "tail\[ \t\]*other_func" } } */ diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-7.c b/gcc/testsuite/gcc.target/riscv/save-restore-7.c new file mode 100644 index 000000000000..06719c4e4138 --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/save-restore-7.c @@ -0,0 +1,30 @@ +/* { dg-options "-Os -msave-restore" } */ + +/* With -msave-restore in use it should not be possible to remove the calls + to the save and restore stubs in this case (in current GCC). */ + +enum + { + VAL_A, + VAL_B, + VAL_C, + VAL_D + } a; + +extern void other_1 (); +extern void other_2 (); + +void func () +{ + switch (a) + { + case VAL_B: + case VAL_C: + other_1 (); + case VAL_D: + other_2 (); + } +} + +/* { dg-final { scan-assembler "call\[ \t\]*t0,__riscv_save_0" } } */ +/* { dg-final { scan-assembler "tail\[ \t\]*__riscv_restore_0" } } */ diff --git a/gcc/testsuite/gcc.target/riscv/save-restore-8.c b/gcc/testsuite/gcc.target/riscv/save-restore-8.c new file mode 100644 index 000000000000..8880cd288eea --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/save-restore-8.c @@ -0,0 +1,12 @@ +/* { dg-options "-Os -msave-restore" } */ + +/* As a leaf function this should never have the calls to the save and + restore stubs added, but lets check anyway. */ + +int func () +{ + return 3; +} + +/* { dg-final { scan-assembler-not "call\[ \t\]*t0,__riscv_save_0" } } */ +/* { dg-final { scan-assembler-not "tail\[ \t\]*__riscv_restore_0" } } */