diff --git a/clang/lib/Driver/ToolChains/Arch/RISCV.cpp b/clang/lib/Driver/ToolChains/Arch/RISCV.cpp --- a/clang/lib/Driver/ToolChains/Arch/RISCV.cpp +++ b/clang/lib/Driver/ToolChains/Arch/RISCV.cpp @@ -438,12 +438,11 @@ Features.push_back("-relax"); // GCC Compatibility: -mno-save-restore is default, unless -msave-restore is - // specified... - if (Args.hasFlag(options::OPT_msave_restore, options::OPT_mno_save_restore, false)) { - // ... but we don't support -msave-restore, so issue a warning. - D.Diag(diag::warn_drv_clang_unsupported) - << Args.getLastArg(options::OPT_msave_restore)->getAsString(Args); - } + // specified. + if (Args.hasFlag(options::OPT_msave_restore, options::OPT_mno_save_restore, false)) + Features.push_back("+save-restore"); + else + Features.push_back("-save-restore"); // Now add any that the user explicitly requested on the command line, // which may override the defaults. diff --git a/clang/test/Driver/riscv-features.c b/clang/test/Driver/riscv-features.c --- a/clang/test/Driver/riscv-features.c +++ b/clang/test/Driver/riscv-features.c @@ -16,9 +16,10 @@ // RUN: %clang -target riscv32-unknown-elf -### %s -msave-restore 2>&1 | FileCheck %s -check-prefix=SAVE-RESTORE // RUN: %clang -target riscv32-unknown-elf -### %s -mno-save-restore 2>&1 | FileCheck %s -check-prefix=NO-SAVE-RESTORE -// SAVE-RESTORE: warning: the clang compiler does not support '-msave-restore' -// NO-SAVE-RESTORE-NOT: warning: the clang compiler does not support -// DEFAULT-NOT: warning: the clang compiler does not support +// SAVE-RESTORE: "-target-feature" "+save-restore" +// NO-SAVE-RESTORE: "-target-feature" "-save-restore" +// DEFAULT: "-target-feature" "-save-restore" +// DEFAULT-NOT: "-target-feature" "+save-restore" // RUN: %clang -target riscv32-linux -### %s -fsyntax-only 2>&1 \ // RUN: | FileCheck %s -check-prefix=DEFAULT-LINUX diff --git a/llvm/lib/Target/RISCV/RISCV.td b/llvm/lib/Target/RISCV/RISCV.td --- a/llvm/lib/Target/RISCV/RISCV.td +++ b/llvm/lib/Target/RISCV/RISCV.td @@ -74,6 +74,9 @@ SubtargetFeature<"reserve-x"#i, "UserReservedRegister[RISCV::X"#i#"]", "true", "Reserve X"#i>; +def FeatureSaveRestore : SubtargetFeature<"save-restore", "EnableSaveRestore", + "true", "Enable save/restore.">; + //===----------------------------------------------------------------------===// // Named operands for CSR instructions. //===----------------------------------------------------------------------===// diff --git a/llvm/lib/Target/RISCV/RISCVFrameLowering.h b/llvm/lib/Target/RISCV/RISCVFrameLowering.h --- a/llvm/lib/Target/RISCV/RISCVFrameLowering.h +++ b/llvm/lib/Target/RISCV/RISCVFrameLowering.h @@ -44,6 +44,15 @@ MachineBasicBlock::iterator eliminateCallFramePseudoInstr(MachineFunction &MF, MachineBasicBlock &MBB, MachineBasicBlock::iterator MI) const override; + bool spillCalleeSavedRegisters(MachineBasicBlock &MBB, + MachineBasicBlock::iterator MI, + const std::vector &CSI, + const TargetRegisterInfo *TRI) const override; + bool + restoreCalleeSavedRegisters(MachineBasicBlock &MBB, + MachineBasicBlock::iterator MI, + std::vector &CSI, + const TargetRegisterInfo *TRI) const override; // Get the first stack adjustment amount for SplitSPAdjust. // Return 0 if we don't want to to split the SP adjustment in prologue and diff --git a/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp b/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp --- a/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp +++ b/llvm/lib/Target/RISCV/RISCVFrameLowering.cpp @@ -23,6 +23,100 @@ using namespace llvm; +// Get the ID of the libcall used for spilling and restoring callee saved +// registers. The ID is representative of the number of registers saved or +// restored by the libcall, except it is zero-indexed - ID 0 corresponds to a +// single register. +static int getLibCallID(const MachineFunction &MF, + const std::vector &CSI) { + const auto *RVFI = MF.getInfo(); + + if (CSI.empty() || !RVFI->useSaveRestoreLibCalls()) + return -1; + + Register MaxReg = RISCV::NoRegister; + for (auto &CS : CSI) + // RISCVRegisterInfo::hasReservedSpillSlot assigns negative frame indexes to + // registers which can be saved by libcall. + if (CS.getFrameIdx() < 0) + MaxReg = std::max(MaxReg.id(), CS.getReg()); + + if (MaxReg == RISCV::NoRegister) + return -1; + + switch (MaxReg) { + default: + llvm_unreachable("Something has gone wrong!"); + case /*s11*/ RISCV::X27: return 12; + case /*s10*/ RISCV::X26: return 11; + case /*s9*/ RISCV::X25: return 10; + case /*s8*/ RISCV::X24: return 9; + case /*s7*/ RISCV::X23: return 8; + case /*s6*/ RISCV::X22: return 7; + case /*s5*/ RISCV::X21: return 6; + case /*s4*/ RISCV::X20: return 5; + case /*s3*/ RISCV::X19: return 4; + case /*s2*/ RISCV::X18: return 3; + case /*s1*/ RISCV::X9: return 2; + case /*s0*/ RISCV::X8: return 1; + case /*ra*/ RISCV::X1: return 0; + } +} + +// Get the name of the libcall used for spilling callee saved registers. +// If this function will not use save/restore libcalls, then return a nullptr. +static const char * +getSpillLibCallName(const MachineFunction &MF, + const std::vector &CSI) { + static const char *const SpillLibCalls[] = { + "__riscv_save_0", + "__riscv_save_1", + "__riscv_save_2", + "__riscv_save_3", + "__riscv_save_4", + "__riscv_save_5", + "__riscv_save_6", + "__riscv_save_7", + "__riscv_save_8", + "__riscv_save_9", + "__riscv_save_10", + "__riscv_save_11", + "__riscv_save_12" + }; + + int LibCallID = getLibCallID(MF, CSI); + if (LibCallID == -1) + return nullptr; + return SpillLibCalls[LibCallID]; +} + +// Get the name of the libcall used for restoring callee saved registers. +// If this function will not use save/restore libcalls, then return a nullptr. +static const char * +getRestoreLibCallName(const MachineFunction &MF, + const std::vector &CSI) { + static const char *const RestoreLibCalls[] = { + "__riscv_restore_0", + "__riscv_restore_1", + "__riscv_restore_2", + "__riscv_restore_3", + "__riscv_restore_4", + "__riscv_restore_5", + "__riscv_restore_6", + "__riscv_restore_7", + "__riscv_restore_8", + "__riscv_restore_9", + "__riscv_restore_10", + "__riscv_restore_11", + "__riscv_restore_12" + }; + + int LibCallID = getLibCallID(MF, CSI); + if (LibCallID == -1) + return nullptr; + return RestoreLibCalls[LibCallID]; +} + bool RISCVFrameLowering::hasFP(const MachineFunction &MF) const { const TargetRegisterInfo *RegInfo = MF.getSubtarget().getRegisterInfo(); @@ -98,6 +192,17 @@ // Returns the register used to hold the stack pointer. static Register getSPReg(const RISCVSubtarget &STI) { return RISCV::X2; } +static SmallVector +getNonLibcallCSI(const std::vector &CSI) { + SmallVector NonLibcallCSI; + + for (auto &CS : CSI) + if (CS.getFrameIdx() >= 0) + NonLibcallCSI.push_back(CS); + + return NonLibcallCSI; +} + void RISCVFrameLowering::emitPrologue(MachineFunction &MF, MachineBasicBlock &MBB) const { MachineFrameInfo &MFI = MF.getFrameInfo(); @@ -115,6 +220,11 @@ Register FPReg = getFPReg(STI); Register SPReg = getSPReg(STI); + // Since spillCalleeSavedRegisters may have inserted a libcall, skip past + // any instructions marked as FrameSetup + while (MBBI != MBB.end() && MBBI->getFlag(MachineInstr::FrameSetup)) + ++MBBI; + // Debug location must be unknown since the first debug location is used // to determine the end of the prologue. DebugLoc DL; @@ -122,12 +232,38 @@ // Determine the correct frame layout determineFrameLayout(MF); + // If libcalls are used to spill and restore callee-saved registers, the frame + // has two sections; the opaque section managed by the libcalls, and the + // section managed by MachineFrameInfo which can also hold callee saved + // registers in fixed stack slots, both of which have negative frame indices. + // This gets even more complicated when incoming arguments are passed via the + // stack, as these too have negative frame indices. An example is detailed + // below: + // + // | incoming arg | <- FI[-3] + // | libcallspill | + // | calleespill | <- FI[-2] + // | calleespill | <- FI[-1] + // | this_frame | <- FI[0] + // + // For negative frame indices, the offset from the frame pointer will differ + // depending on which of these groups the frame index applies to. + // The following calculates the correct offset knowing the number of callee + // saved registers spilt by the two methods. + if (int LibCallRegs = getLibCallID(MF, MFI.getCalleeSavedInfo()) + 1) { + // Calculate the size of the frame managed by the libcall. The libcalls are + // implemented such that the stack will always be 16 byte aligned. + unsigned LibCallFrameSize = alignTo((STI.getXLen() / 8) * LibCallRegs, 16); + RVFI->setLibCallStackSize(LibCallFrameSize); + } + // FIXME (note copied from Lanai): This appears to be overallocating. Needs // investigation. Get the number of bytes to allocate from the FrameInfo. uint64_t StackSize = MFI.getStackSize(); + uint64_t RealStackSize = StackSize + RVFI->getLibCallStackSize(); // Early exit if there is no need to allocate on the stack - if (StackSize == 0 && !MFI.adjustsStack()) + if (RealStackSize == 0 && !MFI.adjustsStack()) return; // If the stack pointer has been marked as reserved, then produce an error if @@ -138,8 +274,10 @@ uint64_t FirstSPAdjustAmount = getFirstSPAdjustAmount(MF); // Split the SP adjustment to reduce the offsets of callee saved spill. - if (FirstSPAdjustAmount) + if (FirstSPAdjustAmount) { StackSize = FirstSPAdjustAmount; + RealStackSize = FirstSPAdjustAmount; + } // Allocate space on the stack if necessary. adjustReg(MBB, MBBI, DL, SPReg, SPReg, -StackSize, MachineInstr::FrameSetup); @@ -150,24 +288,26 @@ BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION)) .addCFIIndex(CFIIndex); - // The frame pointer is callee-saved, and code has been generated for us to - // save it to the stack. We need to skip over the storing of callee-saved - // registers as the frame pointer must be modified after it has been saved - // to the stack, not before. - // FIXME: assumes exactly one instruction is used to save each callee-saved - // register. - const std::vector &CSI = MFI.getCalleeSavedInfo(); - std::advance(MBBI, CSI.size()); - - // Iterate over list of callee-saved registers and emit .cfi_offset - // directives. - for (const auto &Entry : CSI) { - int64_t Offset = MFI.getObjectOffset(Entry.getFrameIdx()); - Register Reg = Entry.getReg(); - unsigned CFIIndex = MF.addFrameInst(MCCFIInstruction::createOffset( - nullptr, RI->getDwarfRegNum(Reg, true), Offset)); - BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION)) - .addCFIIndex(CFIIndex); + const auto &CSI = getNonLibcallCSI(MFI.getCalleeSavedInfo()); + if (!CSI.empty()) { + // The frame pointer is callee-saved, and code has been generated for us to + // save it to the stack. We need to skip over the storing of callee-saved + // registers as the frame pointer must be modified after it has been saved + // to the stack, not before. + // FIXME: assumes exactly one instruction is used to save each callee-saved + // register. + std::advance(MBBI, CSI.size()); + + // Iterate over list of callee-saved registers and emit .cfi_offset + // directives. + for (const auto &Entry : CSI) { + int64_t Offset = MFI.getObjectOffset(Entry.getFrameIdx()); + Register Reg = Entry.getReg(); + unsigned CFIIndex = MF.addFrameInst(MCCFIInstruction::createOffset( + nullptr, RI->getDwarfRegNum(Reg, true), Offset)); + BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION)) + .addCFIIndex(CFIIndex); + } } // Generate new FP. @@ -177,7 +317,8 @@ MF.getFunction(), "Frame pointer required, but has been reserved."}); adjustReg(MBB, MBBI, DL, FPReg, SPReg, - StackSize - RVFI->getVarArgsSaveSize(), MachineInstr::FrameSetup); + RealStackSize - RVFI->getVarArgsSaveSize(), + MachineInstr::FrameSetup); // Emit ".cfi_def_cfa $fp, 0" unsigned CFIIndex = MF.addFrameInst(MCCFIInstruction::createDefCfa( @@ -249,15 +390,26 @@ // last instruction. if (!MBBI->isTerminator()) MBBI = std::next(MBBI); + + // If callee-saved registers are saved via libcall, place stack adjustment + // before this call. + while (MBBI != MBB.begin() && + std::prev(MBBI)->getFlag(MachineInstr::FrameDestroy)) + --MBBI; } + const auto &CSI = getNonLibcallCSI(MFI.getCalleeSavedInfo()); + // Skip to before the restores of callee-saved registers // FIXME: assumes exactly one instruction is used to restore each // callee-saved register. - auto LastFrameDestroy = std::prev(MBBI, MFI.getCalleeSavedInfo().size()); + auto LastFrameDestroy = MBBI; + if (!CSI.empty()) + LastFrameDestroy = std::prev(MBBI, CSI.size()); uint64_t StackSize = MFI.getStackSize(); - uint64_t FPOffset = StackSize - RVFI->getVarArgsSaveSize(); + uint64_t RealStackSize = StackSize + RVFI->getLibCallStackSize(); + uint64_t FPOffset = RealStackSize - RVFI->getVarArgsSaveSize(); // Restore the stack pointer using the value of the frame pointer. Only // necessary if the stack pointer was modified, meaning the stack size is @@ -307,15 +459,16 @@ } // Add CFI directives for callee-saved registers. - const std::vector &CSI = MFI.getCalleeSavedInfo(); - // Iterate over list of callee-saved registers and emit .cfi_restore - // directives. - for (const auto &Entry : CSI) { - Register Reg = Entry.getReg(); - unsigned CFIIndex = MF.addFrameInst(MCCFIInstruction::createRestore( - nullptr, RI->getDwarfRegNum(Reg, true))); - BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION)) - .addCFIIndex(CFIIndex); + if (!CSI.empty()) { + // Iterate over list of callee-saved registers and emit .cfi_restore + // directives. + for (const auto &Entry : CSI) { + Register Reg = Entry.getReg(); + unsigned CFIIndex = MF.addFrameInst(MCCFIInstruction::createRestore( + nullptr, RI->getDwarfRegNum(Reg, true))); + BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION)) + .addCFIIndex(CFIIndex); + } } if (FirstSPAdjustAmount) @@ -342,7 +495,7 @@ // Callee-saved registers should be referenced relative to the stack // pointer (positive offset), otherwise use the frame pointer (negative // offset). - const std::vector &CSI = MFI.getCalleeSavedInfo(); + const auto &CSI = getNonLibcallCSI(MFI.getCalleeSavedInfo()); int MinCSFI = 0; int MaxCSFI = -1; @@ -362,20 +515,27 @@ if (FirstSPAdjustAmount) Offset += FirstSPAdjustAmount; else - Offset += MF.getFrameInfo().getStackSize(); + Offset += MFI.getStackSize(); } else if (RI->needsStackRealignment(MF)) { assert(!MFI.hasVarSizedObjects() && "Unexpected combination of stack realignment and varsized objects"); // If the stack was realigned, the frame pointer is set in order to allow // SP to be restored, but we still access stack objects using SP. FrameReg = RISCV::X2; - Offset += MF.getFrameInfo().getStackSize(); + Offset += MFI.getStackSize(); + if (FI < 0) + Offset += RVFI->getLibCallStackSize(); } else { FrameReg = RI->getFrameRegister(MF); - if (hasFP(MF)) + if (hasFP(MF)) { Offset += RVFI->getVarArgsSaveSize(); - else - Offset += MF.getFrameInfo().getStackSize(); + if (FI >= 0) + Offset -= RVFI->getLibCallStackSize(); + } else { + Offset += MFI.getStackSize(); + if (FI < 0) + Offset += RVFI->getLibCallStackSize(); + } } return Offset; } @@ -488,16 +648,18 @@ // add sp,sp,-64 uint64_t RISCVFrameLowering::getFirstSPAdjustAmount(const MachineFunction &MF) const { + const auto *RVFI = MF.getInfo(); const MachineFrameInfo &MFI = MF.getFrameInfo(); const std::vector &CSI = MFI.getCalleeSavedInfo(); uint64_t StackSize = MFI.getStackSize(); uint64_t StackAlign = getStackAlignment(); - // FIXME: Disable SplitSPAdjust if save-restore libcall enabled when the patch - // landing. The callee saved registers will be pushed by the - // save-restore libcalls, so we don't have to split the SP adjustment - // in this case. - // + // Disable SplitSPAdjust if save-restore libcall used. The callee saved + // registers will be pushed by the save-restore libcalls, so we don't have to + // split the SP adjustment in this case. + if (RVFI->getLibCallStackSize()) + return 0; + // Return the FirstSPAdjustAmount if the StackSize can not fit in signed // 12-bit and there exists a callee saved register need to be pushed. if (!isInt<12>(StackSize) && (CSI.size() > 0)) { @@ -511,3 +673,81 @@ } return 0; } + +bool RISCVFrameLowering::spillCalleeSavedRegisters( + MachineBasicBlock &MBB, MachineBasicBlock::iterator MI, + const std::vector &CSI, + const TargetRegisterInfo *TRI) const { + if (CSI.empty()) + return true; + + MachineFunction *MF = MBB.getParent(); + const TargetInstrInfo &TII = *MF->getSubtarget().getInstrInfo(); + DebugLoc DL; + if (MI != MBB.end() && !MI->isDebugInstr()) + DL = MI->getDebugLoc(); + + const char *SpillLibCall = getSpillLibCallName(*MF, CSI); + if (SpillLibCall) { + // Add spill libcall via non-callee-saved register t0. + BuildMI(MBB, MI, DL, TII.get(RISCV::PseudoCALLReg), RISCV::X5) + .addExternalSymbol(SpillLibCall, RISCVII::MO_CALL) + .setMIFlag(MachineInstr::FrameSetup); + + // Add registers spilled in libcall as liveins. + for (auto &CS : CSI) + MBB.addLiveIn(CS.getReg()); + } + + // Manually spill values not spilled by libcall. + const auto &NonLibcallCSI = getNonLibcallCSI(CSI); + for (auto &CS : NonLibcallCSI) { + // Insert the spill to the stack frame. + Register Reg = CS.getReg(); + const TargetRegisterClass *RC = TRI->getMinimalPhysRegClass(Reg); + TII.storeRegToStackSlot(MBB, MI, Reg, true, CS.getFrameIdx(), RC, TRI); + } + + return true; +} + +bool RISCVFrameLowering::restoreCalleeSavedRegisters( + MachineBasicBlock &MBB, MachineBasicBlock::iterator MI, + std::vector &CSI, const TargetRegisterInfo *TRI) const { + if (CSI.empty()) + return true; + + MachineFunction *MF = MBB.getParent(); + const TargetInstrInfo &TII = *MF->getSubtarget().getInstrInfo(); + DebugLoc DL; + if (MI != MBB.end() && !MI->isDebugInstr()) + DL = MI->getDebugLoc(); + + // Manually restore values not restored by libcall. Insert in reverse order. + // loadRegFromStackSlot can insert multiple instructions. + const auto &NonLibcallCSI = getNonLibcallCSI(CSI); + for (auto &CS : reverse(NonLibcallCSI)) { + Register Reg = CS.getReg(); + const TargetRegisterClass *RC = TRI->getMinimalPhysRegClass(Reg); + TII.loadRegFromStackSlot(MBB, MI, Reg, CS.getFrameIdx(), RC, TRI); + assert(MI != MBB.begin() && "loadRegFromStackSlot didn't insert any code!"); + } + + const char *RestoreLibCall = getRestoreLibCallName(*MF, CSI); + if (RestoreLibCall) { + // Add restore libcall via tail call. + MachineBasicBlock::iterator NewMI = + BuildMI(MBB, MI, DL, TII.get(RISCV::PseudoTAIL)) + .addExternalSymbol(RestoreLibCall, RISCVII::MO_CALL) + .setMIFlag(MachineInstr::FrameDestroy); + + // Remove trailing returns, since the terminator is now a tail call to the + // restore function. + if (MI != MBB.end() && MI->getOpcode() == RISCV::PseudoRET) { + NewMI->copyImplicitOps(*MF, *MI); + MI->eraseFromParent(); + } + } + + return true; +} diff --git a/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h b/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h --- a/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h +++ b/llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h @@ -13,6 +13,7 @@ #ifndef LLVM_LIB_TARGET_RISCV_RISCVMACHINEFUNCTIONINFO_H #define LLVM_LIB_TARGET_RISCV_RISCVMACHINEFUNCTIONINFO_H +#include "RISCVSubtarget.h" #include "llvm/CodeGen/MachineFrameInfo.h" #include "llvm/CodeGen/MachineFunction.h" @@ -30,6 +31,8 @@ /// FrameIndex used for transferring values between 64-bit FPRs and a pair /// of 32-bit GPRs via the stack. int MoveF64FrameIndex = -1; + /// Size of any opaque stack adjustment due to save/restore libcalls. + unsigned LibCallStackSize = 0; public: RISCVMachineFunctionInfo(MachineFunction &MF) : MF(MF) {} @@ -45,6 +48,16 @@ MoveF64FrameIndex = MF.getFrameInfo().CreateStackObject(8, 8, false); return MoveF64FrameIndex; } + + unsigned getLibCallStackSize() const { return LibCallStackSize; } + void setLibCallStackSize(unsigned Size) { LibCallStackSize = Size; } + + bool useSaveRestoreLibCalls() const { + // We cannot use fixed locations for the callee saved spill slots if the + // function uses a varargs save area. + return MF.getSubtarget().enableSaveRestore() && + VarArgsSaveSize == 0 && !MF.getFrameInfo().hasTailCall(); + } }; } // end namespace llvm diff --git a/llvm/lib/Target/RISCV/RISCVRegisterInfo.h b/llvm/lib/Target/RISCV/RISCVRegisterInfo.h --- a/llvm/lib/Target/RISCV/RISCVRegisterInfo.h +++ b/llvm/lib/Target/RISCV/RISCVRegisterInfo.h @@ -37,6 +37,9 @@ const uint32_t *getNoPreservedMask() const override; + bool hasReservedSpillSlot(const MachineFunction &MF, unsigned Reg, + int &FrameIdx) const override; + void eliminateFrameIndex(MachineBasicBlock::iterator MI, int SPAdj, unsigned FIOperandNum, RegScavenger *RS = nullptr) const override; diff --git a/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp b/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp --- a/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp +++ b/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp @@ -12,6 +12,7 @@ #include "RISCVRegisterInfo.h" #include "RISCV.h" +#include "RISCVMachineFunctionInfo.h" #include "RISCVSubtarget.h" #include "llvm/CodeGen/MachineFrameInfo.h" #include "llvm/CodeGen/MachineFunction.h" @@ -100,6 +101,39 @@ return CSR_NoRegs_RegMask; } +// Frame indexes representing locations of CSRs which are given a fixed location +// by save/restore libcalls. +static const std::map FixedCSRFIMap = { + {/*ra*/ RISCV::X1, -1}, + {/*s0*/ RISCV::X8, -2}, + {/*s1*/ RISCV::X9, -3}, + {/*s2*/ RISCV::X18, -4}, + {/*s3*/ RISCV::X19, -5}, + {/*s4*/ RISCV::X20, -6}, + {/*s5*/ RISCV::X21, -7}, + {/*s6*/ RISCV::X22, -8}, + {/*s7*/ RISCV::X23, -9}, + {/*s8*/ RISCV::X24, -10}, + {/*s9*/ RISCV::X25, -11}, + {/*s10*/ RISCV::X26, -12}, + {/*s11*/ RISCV::X27, -13} +}; + +bool RISCVRegisterInfo::hasReservedSpillSlot(const MachineFunction &MF, + unsigned Reg, + int &FrameIdx) const { + const auto *RVFI = MF.getInfo(); + if (!RVFI->useSaveRestoreLibCalls()) + return false; + + auto FII = FixedCSRFIMap.find(Reg); + if (FII == FixedCSRFIMap.end()) + return false; + + FrameIdx = FII->second; + return true; +} + void RISCVRegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II, int SPAdj, unsigned FIOperandNum, RegScavenger *RS) const { diff --git a/llvm/lib/Target/RISCV/RISCVSubtarget.h b/llvm/lib/Target/RISCV/RISCVSubtarget.h --- a/llvm/lib/Target/RISCV/RISCVSubtarget.h +++ b/llvm/lib/Target/RISCV/RISCVSubtarget.h @@ -43,6 +43,7 @@ bool IsRV32E = false; bool EnableLinkerRelax = false; bool EnableRVCHintInstrs = false; + bool EnableSaveRestore = false; unsigned XLen = 32; MVT XLenVT = MVT::i32; RISCVABI::ABI TargetABI = RISCVABI::ABI_Unknown; @@ -91,6 +92,7 @@ bool isRV32E() const { return IsRV32E; } bool enableLinkerRelax() const { return EnableLinkerRelax; } bool enableRVCHintInstrs() const { return EnableRVCHintInstrs; } + bool enableSaveRestore() const { return EnableSaveRestore; } MVT getXLenVT() const { return XLenVT; } unsigned getXLen() const { return XLen; } RISCVABI::ABI getTargetABI() const { return TargetABI; } diff --git a/llvm/test/CodeGen/RISCV/saverestore.ll b/llvm/test/CodeGen/RISCV/saverestore.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/RISCV/saverestore.ll @@ -0,0 +1,299 @@ +; RUN: llc -mtriple=riscv32 < %s | FileCheck %s -check-prefix=RV32I +; RUN: llc -mtriple=riscv64 < %s | FileCheck %s -check-prefix=RV64I +; RUN: llc -mtriple=riscv32 -mattr=+save-restore < %s | FileCheck %s -check-prefix=RV32I-SR +; RUN: llc -mtriple=riscv64 -mattr=+save-restore < %s | FileCheck %s -check-prefix=RV64I-SR +; RUN: llc -mtriple=riscv32 -mattr=+f,+save-restore -target-abi=ilp32f < %s | FileCheck %s -check-prefix=RV32I-FP-SR +; RUN: llc -mtriple=riscv64 -mattr=+f,+d,+save-restore -target-abi=lp64d < %s | FileCheck %s -check-prefix=RV64I-FP-SR + +; Check that the correct save/restore libcalls are generated. + +@var0 = global [18 x i32] zeroinitializer +@var1 = global [24 x i32] zeroinitializer +@var2 = global [30 x i32] zeroinitializer + +define void @callee_saved0() nounwind { +; RV32I-LABEL: callee_saved0: +; RV32I-NOT: call t0, __riscv_save +; RV32I-NOT: tail __riscv_restore +; +; RV64I-LABEL: callee_saved0: +; RV64I-NOT: call t0, __riscv_save +; RV64I-NOT: tail __riscv_restore +; +; RV32I-SR-LABEL: callee_saved0: +; RV32I-SR: call t0, __riscv_save_5 +; RV32I-SR: tail __riscv_restore_5 +; +; RV64I-SR-LABEL: callee_saved0: +; RV64I-SR: call t0, __riscv_save_5 +; RV64I-SR: tail __riscv_restore_5 +; +; RV32I-FP-SR-LABEL: callee_saved0: +; RV32I-FP-SR: call t0, __riscv_save_5 +; RV32I-FP-SR: tail __riscv_restore_5 +; +; RV64I-FP-SR-LABEL: callee_saved0: +; RV64I-FP-SR: call t0, __riscv_save_5 +; RV64I-FP-SR: tail __riscv_restore_5 + %val = load [18 x i32], [18 x i32]* @var0 + store volatile [18 x i32] %val, [18 x i32]* @var0 + ret void +} + +define void @callee_saved1() nounwind { +; RV32I-LABEL: callee_saved1: +; RV32I-NOT: call t0, __riscv_save +; RV32I-NOT: tail __riscv_restore +; +; RV64I-LABEL: callee_saved1: +; RV64I-NOT: call t0, __riscv_save +; RV64I-NOT: tail __riscv_restore +; +; RV32I-SR-LABEL: callee_saved1: +; RV32I-SR: call t0, __riscv_save_11 +; RV32I-SR: tail __riscv_restore_11 +; +; RV64I-SR-LABEL: callee_saved1: +; RV64I-SR: call t0, __riscv_save_11 +; RV64I-SR: tail __riscv_restore_11 +; +; RV32I-FP-SR-LABEL: callee_saved1: +; RV32I-FP-SR: call t0, __riscv_save_11 +; RV32I-FP-SR: tail __riscv_restore_11 +; +; RV64I-FP-SR-LABEL: callee_saved1: +; RV64I-FP-SR: call t0, __riscv_save_11 +; RV64I-FP-SR: tail __riscv_restore_11 + %val = load [24 x i32], [24 x i32]* @var1 + store volatile [24 x i32] %val, [24 x i32]* @var1 + ret void +} + +define void @callee_saved2() nounwind { +; RV32I-LABEL: callee_saved2: +; RV32I-NOT: call t0, __riscv_save +; RV32I-NOT: tail __riscv_restore +; +; RV64I-LABEL: callee_saved2: +; RV64I-NOT: call t0, __riscv_save +; RV64I-NOT: tail __riscv_restore +; +; RV32I-SR-LABEL: callee_saved2: +; RV32I-SR: call t0, __riscv_save_12 +; RV32I-SR: tail __riscv_restore_12 +; +; RV64I-SR-LABEL: callee_saved2: +; RV64I-SR: call t0, __riscv_save_12 +; RV64I-SR: tail __riscv_restore_12 +; +; RV32I-FP-SR-LABEL: callee_saved2: +; RV32I-FP-SR: call t0, __riscv_save_12 +; RV32I-FP-SR: tail __riscv_restore_12 +; +; RV64I-FP-SR-LABEL: callee_saved2: +; RV64I-FP-SR: call t0, __riscv_save_12 +; RV64I-FP-SR: tail __riscv_restore_12 + %val = load [30 x i32], [30 x i32]* @var2 + store volatile [30 x i32] %val, [30 x i32]* @var2 + ret void +} + +; Check that floating point callee saved registers are still manually saved and +; restored. + +define void @callee_saved_fp() nounwind { +; RV32I-LABEL: callee_saved_fp: +; RV32I-NOT: call t0, __riscv_save +; RV32I-NOT: tail __riscv_restore +; +; RV64I-LABEL: callee_saved_fp: +; RV64I-NOT: call t0, __riscv_save +; RV64I-NOT: tail __riscv_restore +; +; RV32I-SR-LABEL: callee_saved_fp: +; RV32I-SR: call t0, __riscv_save_7 +; RV32I-SR: tail __riscv_restore_7 +; +; RV64I-SR-LABEL: callee_saved_fp: +; RV64I-SR: call t0, __riscv_save_7 +; RV64I-SR: tail __riscv_restore_7 +; +; RV32I-FP-SR-LABEL: callee_saved_fp: +; RV32I-FP-SR: call t0, __riscv_save_7 +; RV32I-FP-SR-NEXT: addi sp, sp, -16 +; RV32I-FP-SR-NEXT: fsw fs0, 12(sp) +; RV32I-FP-SR: flw fs0, 12(sp) +; RV32I-FP-SR-NEXT: addi sp, sp, 16 +; RV32I-FP-SR-NEXT: tail __riscv_restore_7 +; +; RV64I-FP-SR-LABEL: callee_saved_fp: +; RV64I-FP-SR: call t0, __riscv_save_7 +; RV64I-FP-SR-NEXT: addi sp, sp, -16 +; RV64I-FP-SR-NEXT: fsd fs0, 8(sp) +; RV64I-FP-SR: fld fs0, 8(sp) +; RV64I-FP-SR-NEXT: addi sp, sp, 16 +; RV64I-FP-SR-NEXT: tail __riscv_restore_7 + call void asm sideeffect "", "~{f8},~{x9},~{x18},~{x19},~{x20},~{x21},~{x22}"() + ret void +} + +; Check that preserving tail calls is preferred over save/restore + +declare i32 @tail_callee(i32 %i) + +define i32 @tail_call(i32 %i) nounwind { +; RV32I-LABEL: tail_call: +; RV32I-NOT: call t0, __riscv_save +; RV32I: tail tail_callee +; RV32I-NOT: tail __riscv_restore +; +; RV64I-LABEL: tail_call: +; RV64I-NOT: call t0, __riscv_save +; RV64I: tail tail_callee +; RV64I-NOT: tail __riscv_restore +; +; RV32I-SR-LABEL: tail_call: +; RV32I-SR-NOT: call t0, __riscv_save +; RV32I-SR: tail tail_callee +; RV32I-SR-NOT: tail __riscv_restore +; +; RV64I-SR-LABEL: tail_call: +; RV64I-SR-NOT: call t0, __riscv_save +; RV64I-SR: tail tail_callee +; RV64I-SR-NOT: tail __riscv_restore +; +; RV32I-FP-SR-LABEL: tail_call: +; RV32I-FP-SR-NOT: call t0, __riscv_save +; RV32I-FP-SR: tail tail_callee +; RV32I-FP-SR-NOT: tail __riscv_restore +; +; RV64I-FP-SR-LABEL: tail_call: +; RV64I-FP-SR-NOT: call t0, __riscv_save +; RV64I-FP-SR: tail tail_callee +; RV64I-FP-SR-NOT: tail __riscv_restore +entry: + %val = load [18 x i32], [18 x i32]* @var0 + store volatile [18 x i32] %val, [18 x i32]* @var0 + %r = tail call i32 @tail_callee(i32 %i) + ret i32 %r +} + +; Check that functions with varargs do not use save/restore code + +declare void @llvm.va_start(i8*) +declare void @llvm.va_end(i8*) + +define i32 @varargs(i8* %fmt, ...) nounwind { +; RV32I-LABEL: varargs: +; RV32I-NOT: call t0, __riscv_save +; RV32I-NOT: tail __riscv_restore +; +; RV64I-LABEL: varargs: +; RV64I-NOT: call t0, __riscv_save +; RV64I-NOT: tail __riscv_restore +; +; RV32I-SR-LABEL: varargs: +; RV32I-SR-NOT: call t0, __riscv_save +; RV32I-SR-NOT: tail __riscv_restore +; +; RV64I-SR-LABEL: varargs: +; RV64I-SR-NOT: call t0, __riscv_save +; RV64I-SR-NOT: tail __riscv_restore +; +; RV32I-FP-SR-LABEL: varargs: +; RV32I-FP-SR-NOT: call t0, __riscv_save +; RV32I-FP-SR-NOT: tail __riscv_restore +; +; RV64I-FP-SR-LABEL: varargs: +; RV64I-FP-SR-NOT: call t0, __riscv_save +; RV64I-FP-SR-NOT: tail __riscv_restore + %va = alloca i8*, align 4 + %1 = bitcast i8** %va to i8* + call void @llvm.va_start(i8* %1) + %argp.cur = load i8*, i8** %va, align 4 + %argp.next = getelementptr inbounds i8, i8* %argp.cur, i32 4 + store i8* %argp.next, i8** %va, align 4 + %2 = bitcast i8* %argp.cur to i32* + %3 = load i32, i32* %2, align 4 + call void @llvm.va_end(i8* %1) + ret i32 %3 +} + +define void @many_args(i32, i32, i32, i32, i32, i32, i32, i32, i32) nounwind { +; RV32I-LABEL: many_args: +; RV32I-NOT: call t0, __riscv_save +; RV32I-NOT: tail __riscv_restore +; +; RV64I-LABEL: many_args: +; RV64I-NOT: call t0, __riscv_save +; RV64I-NOT: tail __riscv_restore +; +; RV32I-SR-LABEL: many_args: +; RV32I-SR: call t0, __riscv_save_5 +; RV32I-SR: tail __riscv_restore_5 +; +; RV64I-SR-LABEL: many_args: +; RV64I-SR: call t0, __riscv_save_5 +; RV64I-SR: tail __riscv_restore_5 +; +; RV32I-FP-SR-LABEL: many_args: +; RV32I-FP-SR: call t0, __riscv_save_5 +; RV32I-FP-SR: tail __riscv_restore_5 +; +; RV64I-FP-SR-LABEL: many_args: +; RV64I-FP-SR: call t0, __riscv_save_5 +; RV64I-FP-SR: tail __riscv_restore_5 +entry: + %val = load [18 x i32], [18 x i32]* @var0 + store volatile [18 x i32] %val, [18 x i32]* @var0 + ret void +} + +; Check that dynamic allocation calculations remain correct + +declare i8* @llvm.stacksave() +declare void @llvm.stackrestore(i8*) +declare void @notdead(i8*) + +define void @alloca(i32 %n) nounwind { +; RV32I-LABEL: alloca: +; RV32I-NOT: call t0, __riscv_save +; RV32I: addi s0, sp, 16 +; RV32I: addi sp, s0, -16 +; RV32I-NOT: tail __riscv_restore +; +; RV64I-LABEL: alloca: +; RV64I-NOT: call t0, __riscv_save +; RV64I: addi s0, sp, 32 +; RV64I: addi sp, s0, -32 +; RV64I-NOT: tail __riscv_restore +; +; RV32I-SR-LABEL: alloca: +; RV32I-SR: call t0, __riscv_save_2 +; RV32I-SR: addi s0, sp, 16 +; RV32I-SR: addi sp, s0, -16 +; RV32I-SR: tail __riscv_restore_2 +; +; RV64I-SR-LABEL: alloca: +; RV64I-SR: call t0, __riscv_save_2 +; RV64I-SR: addi s0, sp, 32 +; RV64I-SR: addi sp, s0, -32 +; RV64I-SR: tail __riscv_restore_2 +; +; RV32I-FP-SR-LABEL: alloca: +; RV32I-FP-SR: call t0, __riscv_save_2 +; RV32I-FP-SR: addi s0, sp, 16 +; RV32I-FP-SR: addi sp, s0, -16 +; RV32I-FP-SR: tail __riscv_restore_2 +; +; RV64I-FP-SR-LABEL: alloca: +; RV64I-FP-SR: call t0, __riscv_save_2 +; RV64I-FP-SR: addi s0, sp, 32 +; RV64I-FP-SR: addi sp, s0, -32 +; RV64I-FP-SR: tail __riscv_restore_2 + %sp = call i8* @llvm.stacksave() + %addr = alloca i8, i32 %n + call void @notdead(i8* %addr) + call void @llvm.stackrestore(i8* %sp) + ret void +}