diff --git a/lib/Target/RISCV/RISCV.td b/lib/Target/RISCV/RISCV.td index 46530a8f74a..70d87d662f9 100644 --- a/lib/Target/RISCV/RISCV.td +++ b/lib/Target/RISCV/RISCV.td @@ -69,6 +69,11 @@ def FeatureRelax : SubtargetFeature<"relax", "EnableLinkerRelax", "true", "Enable Linker relaxation.">; +foreach i = {1-31} in + def FeatureReserveX#i : + SubtargetFeature<"reserve-x"#i, "UserReservedRegister[RISCV::X"#i#"]", + "true", "Reserve X"#i>; + //===----------------------------------------------------------------------===// // Named operands for CSR instructions. //===----------------------------------------------------------------------===// diff --git a/lib/Target/RISCV/RISCVFrameLowering.cpp b/lib/Target/RISCV/RISCVFrameLowering.cpp index 6b6f62e18ce..daa7b7a7ea9 100644 --- a/lib/Target/RISCV/RISCVFrameLowering.cpp +++ b/lib/Target/RISCV/RISCVFrameLowering.cpp @@ -18,6 +18,7 @@ #include "llvm/CodeGen/MachineInstrBuilder.h" #include "llvm/CodeGen/MachineRegisterInfo.h" #include "llvm/CodeGen/RegisterScavenging.h" +#include "llvm/IR/DiagnosticInfo.h" #include "llvm/MC/MCDwarf.h" using namespace llvm; @@ -131,6 +132,12 @@ void RISCVFrameLowering::emitPrologue(MachineFunction &MF, if (StackSize == 0 && !MFI.adjustsStack()) return; + // If the stack pointer has been marked as reserved, then produce an error if + // the frame requires stack allocation + if (STI.isRegisterReservedByUser(SPReg)) + MF.getFunction().getContext().diagnose(DiagnosticInfoUnsupported{ + MF.getFunction(), "Stack pointer required, but has been reserved."}); + uint64_t FirstSPAdjustAmount = getFirstSPAdjustAmount(MF); // Split the SP adjustment to reduce the offsets of callee saved spill. if (FirstSPAdjustAmount) @@ -167,6 +174,10 @@ void RISCVFrameLowering::emitPrologue(MachineFunction &MF, // Generate new FP. if (hasFP(MF)) { + if (STI.isRegisterReservedByUser(FPReg)) + MF.getFunction().getContext().diagnose(DiagnosticInfoUnsupported{ + MF.getFunction(), "Frame pointer required, but has been reserved."}); + adjustReg(MBB, MBBI, DL, FPReg, SPReg, StackSize - RVFI->getVarArgsSaveSize(), MachineInstr::FrameSetup); diff --git a/lib/Target/RISCV/RISCVISelLowering.cpp b/lib/Target/RISCV/RISCVISelLowering.cpp index dc829fce901..b56d95132e0 100644 --- a/lib/Target/RISCV/RISCVISelLowering.cpp +++ b/lib/Target/RISCV/RISCVISelLowering.cpp @@ -2247,6 +2247,16 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI, Glue = Chain.getValue(1); } + // Validate that none of the argument registers have been marked as + // reserved, if so report an error. Do the same for the return address if this + // is not a tailcall. + validateCCReservedRegs(RegsToPass, MF); + if (!IsTailCall && + MF.getSubtarget().isRegisterReservedByUser(RISCV::X1)) + MF.getFunction().getContext().diagnose(DiagnosticInfoUnsupported{ + MF.getFunction(), + "Return address register required, but has been reserved."}); + // If the callee is a GlobalAddress/ExternalSymbol node, turn it into a // TargetGlobalAddress/TargetExternalSymbol node so that legalize won't // split it and then direct call can be matched by PseudoCALL. @@ -2362,6 +2372,9 @@ RISCVTargetLowering::LowerReturn(SDValue Chain, CallingConv::ID CallConv, const SmallVectorImpl &Outs, const SmallVectorImpl &OutVals, const SDLoc &DL, SelectionDAG &DAG) const { + const MachineFunction &MF = DAG.getMachineFunction(); + const RISCVSubtarget &STI = MF.getSubtarget(); + // Stores the assignment of the return value to a location. SmallVector RVLocs; @@ -2391,6 +2404,13 @@ RISCVTargetLowering::LowerReturn(SDValue Chain, CallingConv::ID CallConv, Register RegLo = VA.getLocReg(); assert(RegLo < RISCV::X31 && "Invalid register pair"); Register RegHi = RegLo + 1; + + if (STI.isRegisterReservedByUser(RegLo) || + STI.isRegisterReservedByUser(RegHi)) + MF.getFunction().getContext().diagnose(DiagnosticInfoUnsupported{ + MF.getFunction(), + "Return value register required, but has been reserved."}); + Chain = DAG.getCopyToReg(Chain, DL, RegLo, Lo, Glue); Glue = Chain.getValue(1); RetOps.push_back(DAG.getRegister(RegLo, MVT::i32)); @@ -2402,6 +2422,11 @@ RISCVTargetLowering::LowerReturn(SDValue Chain, CallingConv::ID CallConv, Val = convertValVTToLocVT(DAG, Val, VA, DL); Chain = DAG.getCopyToReg(Chain, DL, VA.getLocReg(), Val, Glue); + if (STI.isRegisterReservedByUser(VA.getLocReg())) + MF.getFunction().getContext().diagnose(DiagnosticInfoUnsupported{ + MF.getFunction(), + "Return value register required, but has been reserved."}); + // Guarantee that all emitted copies are stuck together. Glue = Chain.getValue(1); RetOps.push_back(DAG.getRegister(VA.getLocReg(), VA.getLocVT())); @@ -2440,6 +2465,19 @@ RISCVTargetLowering::LowerReturn(SDValue Chain, CallingConv::ID CallConv, return DAG.getNode(RISCVISD::RET_FLAG, DL, MVT::Other, RetOps); } +void RISCVTargetLowering::validateCCReservedRegs( + const SmallVectorImpl> &Regs, + MachineFunction &MF) const { + const Function &F = MF.getFunction(); + const RISCVSubtarget &STI = MF.getSubtarget(); + + if (std::any_of(std::begin(Regs), std::end(Regs), [&STI](auto Reg) { + return STI.isRegisterReservedByUser(Reg.first); + })) + F.getContext().diagnose(DiagnosticInfoUnsupported{ + F, "Argument register required, but has been reserved."}); +} + const char *RISCVTargetLowering::getTargetNodeName(unsigned Opcode) const { switch ((RISCVISD::NodeType)Opcode) { case RISCVISD::FIRST_NUMBER: diff --git a/lib/Target/RISCV/RISCVISelLowering.h b/lib/Target/RISCV/RISCVISelLowering.h index 18fc7350bbb..0d2d14e8d67 100644 --- a/lib/Target/RISCV/RISCVISelLowering.h +++ b/lib/Target/RISCV/RISCVISelLowering.h @@ -210,6 +210,12 @@ private: Value *AlignedAddr, Value *CmpVal, Value *NewVal, Value *Mask, AtomicOrdering Ord) const override; + + /// Generate error diagnostics if any register used by CC has been marked + /// reserved. + void validateCCReservedRegs( + const SmallVectorImpl> &Regs, + MachineFunction &MF) const; }; } diff --git a/lib/Target/RISCV/RISCVRegisterInfo.cpp b/lib/Target/RISCV/RISCVRegisterInfo.cpp index 66557687c0b..65a7e41a23d 100644 --- a/lib/Target/RISCV/RISCVRegisterInfo.cpp +++ b/lib/Target/RISCV/RISCVRegisterInfo.cpp @@ -69,6 +69,12 @@ BitVector RISCVRegisterInfo::getReservedRegs(const MachineFunction &MF) const { const TargetFrameLowering *TFI = getFrameLowering(MF); BitVector Reserved(getNumRegs()); + // Mark any registers requested to be reserved as such + for (size_t Reg = 0; Reg < getNumRegs(); Reg++) { + if (MF.getSubtarget().isRegisterReservedByUser(Reg)) + markSuperRegs(Reserved, Reg); + } + // Use markSuperRegs to ensure any register aliases are also reserved markSuperRegs(Reserved, RISCV::X0); // zero markSuperRegs(Reserved, RISCV::X1); // ra @@ -81,6 +87,11 @@ BitVector RISCVRegisterInfo::getReservedRegs(const MachineFunction &MF) const { return Reserved; } +bool RISCVRegisterInfo::isAsmClobberable(const MachineFunction &MF, + unsigned PhysReg) const { + return !MF.getSubtarget().isRegisterReservedByUser(PhysReg); +} + bool RISCVRegisterInfo::isConstantPhysReg(unsigned PhysReg) const { return PhysReg == RISCV::X0; } diff --git a/lib/Target/RISCV/RISCVRegisterInfo.h b/lib/Target/RISCV/RISCVRegisterInfo.h index 56a50fe6ddc..30b639517fd 100644 --- a/lib/Target/RISCV/RISCVRegisterInfo.h +++ b/lib/Target/RISCV/RISCVRegisterInfo.h @@ -30,6 +30,8 @@ struct RISCVRegisterInfo : public RISCVGenRegisterInfo { const MCPhysReg *getCalleeSavedRegs(const MachineFunction *MF) const override; BitVector getReservedRegs(const MachineFunction &MF) const override; + bool isAsmClobberable(const MachineFunction &MF, + unsigned PhysReg) const override; bool isConstantPhysReg(unsigned PhysReg) const override; diff --git a/lib/Target/RISCV/RISCVSubtarget.cpp b/lib/Target/RISCV/RISCVSubtarget.cpp index f114c6ac192..83e7e2d52cc 100644 --- a/lib/Target/RISCV/RISCVSubtarget.cpp +++ b/lib/Target/RISCV/RISCVSubtarget.cpp @@ -50,6 +50,7 @@ RISCVSubtarget &RISCVSubtarget::initializeSubtargetDependencies( RISCVSubtarget::RISCVSubtarget(const Triple &TT, StringRef CPU, StringRef FS, StringRef ABIName, const TargetMachine &TM) : RISCVGenSubtargetInfo(TT, CPU, FS), + UserReservedRegister(RISCV::NUM_TARGET_REGS), FrameLowering(initializeSubtargetDependencies(TT, CPU, FS, ABIName)), InstrInfo(*this), RegInfo(getHwMode()), TLInfo(TM, *this) { CallLoweringInfo.reset(new RISCVCallLowering(*getTargetLowering())); diff --git a/lib/Target/RISCV/RISCVSubtarget.h b/lib/Target/RISCV/RISCVSubtarget.h index 7d0373a5253..605d4abcc9a 100644 --- a/lib/Target/RISCV/RISCVSubtarget.h +++ b/lib/Target/RISCV/RISCVSubtarget.h @@ -46,6 +46,7 @@ class RISCVSubtarget : public RISCVGenSubtargetInfo { unsigned XLen = 32; MVT XLenVT = MVT::i32; RISCVABI::ABI TargetABI = RISCVABI::ABI_Unknown; + BitVector UserReservedRegister; RISCVFrameLowering FrameLowering; RISCVInstrInfo InstrInfo; RISCVRegisterInfo RegInfo; @@ -93,6 +94,10 @@ public: MVT getXLenVT() const { return XLenVT; } unsigned getXLen() const { return XLen; } RISCVABI::ABI getTargetABI() const { return TargetABI; } + bool isRegisterReservedByUser(Register i) const { + assert(i < RISCV::NUM_TARGET_REGS && "Register out of range"); + return UserReservedRegister[i]; + } protected: // GlobalISel related APIs. diff --git a/test/CodeGen/RISCV/reserved-reg-errors.ll b/test/CodeGen/RISCV/reserved-reg-errors.ll new file mode 100644 index 00000000000..f10ef9ab3e0 --- /dev/null +++ b/test/CodeGen/RISCV/reserved-reg-errors.ll @@ -0,0 +1,36 @@ +; RUN: not llc -mtriple=riscv32 -mattr=+reserve-x1 < %s 2>&1 | FileCheck %s -check-prefix=X1 +; RUN: not llc -mtriple=riscv64 -mattr=+reserve-x1 < %s 2>&1 | FileCheck %s -check-prefix=X1 +; RUN: not llc -mtriple=riscv32 -mattr=+reserve-x2 < %s 2>&1 | FileCheck %s -check-prefix=X2 +; RUN: not llc -mtriple=riscv64 -mattr=+reserve-x2 < %s 2>&1 | FileCheck %s -check-prefix=X2 +; RUN: not llc -mtriple=riscv32 -mattr=+reserve-x8 < %s 2>&1 | FileCheck %s -check-prefix=X8 +; RUN: not llc -mtriple=riscv64 -mattr=+reserve-x8 < %s 2>&1 | FileCheck %s -check-prefix=X8 +; RUN: not llc -mtriple=riscv32 -mattr=+reserve-x10 < %s 2>&1 | FileCheck %s -check-prefix=X10 +; RUN: not llc -mtriple=riscv64 -mattr=+reserve-x10 < %s 2>&1 | FileCheck %s -check-prefix=X10 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x11 < %s 2>&1 | FileCheck %s -check-prefix=X11 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x11 < %s 2>&1 | FileCheck %s -check-prefix=X11 +; RUN: llc -mtriple=riscv32 <%s +; RUN: llc -mtriple=riscv64 <%s + +; This tests combinations when we would expect an error to be produced because +; a reserved register is required by the default ABI. The final test checks no +; errors are produced when no registers are reserved. + +define i32 @caller(i32 %a) #0 { +; X1: in function caller {{.*}} Return address register required, but has been reserved. +; X2: in function caller {{.*}} Stack pointer required, but has been reserved. +; X8: in function caller {{.*}} Frame pointer required, but has been reserved. +; X10: in function caller {{.*}} Argument register required, but has been reserved. +; X10: in function caller {{.*}} Return value register required, but has been reserved. + %call = call i32 @callee(i32 0) + ret i32 %call +} + +declare i32 @callee(i32 %a) + +define void @clobber() { +; X11: warning: inline asm clobber list contains reserved registers: X11 + call void asm sideeffect "nop", "~{x11}"() + ret void +} + +attributes #0 = { "frame-pointer"="all" } diff --git a/test/CodeGen/RISCV/reserved-regs.ll b/test/CodeGen/RISCV/reserved-regs.ll new file mode 100644 index 00000000000..a4d09904ad9 --- /dev/null +++ b/test/CodeGen/RISCV/reserved-regs.ll @@ -0,0 +1,130 @@ +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x3 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X3 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x3 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X3 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x4 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X4 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x4 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X4 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x5 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X5 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x5 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X5 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x6 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X6 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x6 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X6 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x7 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X7 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x7 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X7 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x8 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X8 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x8 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X8 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x9 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X9 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x9 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X9 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x10 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X10 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x10 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X10 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x11 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X11 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x11 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X11 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x12 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X12 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x12 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X12 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x13 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X13 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x13 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X13 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x14 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X14 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x14 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X14 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x15 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X15 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x15 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X15 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x16 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X16 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x16 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X16 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x17 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X17 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x17 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X17 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x18 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X18 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x18 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X18 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x19 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X19 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x19 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X19 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x20 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X20 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x20 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X20 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x21 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X21 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x21 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X21 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x22 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X22 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x22 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X22 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x23 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X23 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x23 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X23 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x24 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X24 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x24 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X24 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x25 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X25 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x25 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X25 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x26 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X26 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x26 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X26 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x27 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X27 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x27 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X27 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x28 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X28 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x28 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X28 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x29 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X29 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x29 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X29 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x30 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X30 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x30 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X30 +; RUN: llc -mtriple=riscv32 -mattr=+reserve-x31 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X31 +; RUN: llc -mtriple=riscv64 -mattr=+reserve-x31 -verify-machineinstrs < %s | FileCheck %s -check-prefix=X31 + +; This program is free to use all registers, but needs a stack pointer for +; spill values, so do not test for reserving the stack pointer. + +; Used to exhaust all registers +@var = global [32 x i64] zeroinitializer + +define void @foo() { + %1 = load volatile [32 x i64], [32 x i64]* @var + store volatile [32 x i64] %1, [32 x i64]* @var + +; X3-NOT: lw gp, +; X3-NOT: ld gp, +; X4-NOT: lw tp, +; X4-NOT: ld tp, +; X5-NOT: lw t0, +; X5-NOT: ld t0, +; X6-NOT: lw t1, +; X6-NOT: ld t1, +; X7-NOT: lw t2, +; X7-NOT: ld t2, +; X8-NOT: lw s0, +; X8-NOT: ld s0, +; X9-NOT: lw s1, +; X9-NOT: ld s1, +; X10-NOT: lw a0, +; X10-NOT: ld a0, +; X11-NOT: lw a1, +; X11-NOT: ld a1, +; X12-NOT: lw a2, +; X12-NOT: ld a2, +; X13-NOT: lw a3, +; X13-NOT: ld a3, +; X14-NOT: lw a4, +; X14-NOT: ld a4, +; X15-NOT: lw a5, +; X15-NOT: ld a5, +; X16-NOT: lw a6, +; X16-NOT: ld a6, +; X17-NOT: lw a7, +; X17-NOT: ld a7, +; X18-NOT: lw s2, +; X18-NOT: ld s2, +; X19-NOT: lw s3, +; X19-NOT: ld s3, +; X20-NOT: lw s4, +; X20-NOT: ld s4, +; X21-NOT: lw s5, +; X21-NOT: ld s5, +; X22-NOT: lw s6, +; X22-NOT: ld s6, +; X23-NOT: lw s7, +; X23-NOT: ld s7, +; X24-NOT: lw s8, +; X24-NOT: ld s8, +; X25-NOT: lw s9, +; X25-NOT: ld s9, +; X26-NOT: lw s10, +; X26-NOT: ld s10, +; X27-NOT: lw s11, +; X27-NOT: ld s11, +; X28-NOT: lw t3, +; X28-NOT: ld t3, +; X29-NOT: lw t4, +; X29-NOT: ld t4, +; X30-NOT: lw t5, +; X30-NOT: ld t5, +; X31-NOT: lw t6, +; X31-NOT: ld t6, + + ret void +}