[RISCV] Add support for _interrupt attribute

- Save/restore only registers that are used.
This includes Callee saved registers and Caller saved registers
(arguments and temporaries) for integer and FP registers.
- If there is a call in the interrupt handler, save/restore all
Caller saved registers (arguments and temporaries) and all FP registers.
- Emit special return instructions depending on "interrupt"
attribute type.
Based on initial patch by Zhaoshi Zheng.

Reviewers: asb

Reviewed By: asb

Subscribers: rkruppe, the_o, MartinMosbeck, brucehoult, rbar, johnrusso, simoncook, sabuasal, niosHD, kito-cheng, shiva0217, zzheng, edward-jones, mgrang, rogfer01, llvm-commits

Differential Revision: https://reviews.llvm.org/D48411

llvm-svn: 338047
This commit is contained in:
Ana Pazos 2018-07-26 17:49:43 +00:00
parent 09810c9269
commit 2e4106b73d
12 changed files with 1459 additions and 3 deletions

View File

@ -18,3 +18,40 @@ def CSR : CalleeSavedRegs<(add X1, X3, X4, X8, X9, (sequence "X%u", 18, 27))>;
// Needed for implementation of RISCVRegisterInfo::getNoPreservedMask()
def CSR_NoRegs : CalleeSavedRegs<(add)>;
// Interrupt handler needs to save/restore all registers that are used,
// both Caller and Callee saved registers.
def CSR_Interrupt : CalleeSavedRegs<(add X1,
(sequence "X%u", 3, 9),
(sequence "X%u", 10, 11),
(sequence "X%u", 12, 17),
(sequence "X%u", 18, 27),
(sequence "X%u", 28, 31))>;
// Same as CSR_Interrupt, but including all 32-bit FP registers.
def CSR_XLEN_F32_Interrupt: CalleeSavedRegs<(add X1,
(sequence "X%u", 3, 9),
(sequence "X%u", 10, 11),
(sequence "X%u", 12, 17),
(sequence "X%u", 18, 27),
(sequence "X%u", 28, 31),
(sequence "F%u_32", 0, 7),
(sequence "F%u_32", 10, 11),
(sequence "F%u_32", 12, 17),
(sequence "F%u_32", 28, 31),
(sequence "F%u_32", 8, 9),
(sequence "F%u_32", 18, 27))>;
// Same as CSR_Interrupt, but including all 64-bit FP registers.
def CSR_XLEN_F64_Interrupt: CalleeSavedRegs<(add X1,
(sequence "X%u", 3, 9),
(sequence "X%u", 10, 11),
(sequence "X%u", 12, 17),
(sequence "X%u", 18, 27),
(sequence "X%u", 28, 31),
(sequence "F%u_64", 0, 7),
(sequence "F%u_64", 10, 11),
(sequence "F%u_64", 12, 17),
(sequence "F%u_64", 28, 31),
(sequence "F%u_64", 8, 9),
(sequence "F%u_64", 18, 27))>;

View File

@ -212,6 +212,36 @@ void RISCVFrameLowering::determineCalleeSaves(MachineFunction &MF,
SavedRegs.set(RISCV::X1);
SavedRegs.set(RISCV::X8);
}
// If interrupt is enabled and there are calls in the handler,
// unconditionally save all Caller-saved registers and
// all FP registers, regardless whether they are used.
MachineFrameInfo &MFI = MF.getFrameInfo();
if (MF.getFunction().hasFnAttribute("interrupt") && MFI.hasCalls()) {
static const MCPhysReg CSRegs[] = { RISCV::X1, /* ra */
RISCV::X5, RISCV::X6, RISCV::X7, /* t0-t2 */
RISCV::X10, RISCV::X11, /* a0-a1, a2-a7 */
RISCV::X12, RISCV::X13, RISCV::X14, RISCV::X15, RISCV::X16, RISCV::X17,
RISCV::X28, RISCV::X29, RISCV::X30, RISCV::X31, 0 /* t3-t6 */
};
for (unsigned i = 0; CSRegs[i]; ++i)
SavedRegs.set(CSRegs[i]);
if (MF.getSubtarget<RISCVSubtarget>().hasStdExtD() ||
MF.getSubtarget<RISCVSubtarget>().hasStdExtF()) {
// If interrupt is enabled, this list contains all FP registers.
const MCPhysReg * Regs = MF.getRegInfo().getCalleeSavedRegs();
for (unsigned i = 0; Regs[i]; ++i)
if (RISCV::FPR32RegClass.contains(Regs[i]) ||
RISCV::FPR64RegClass.contains(Regs[i]))
SavedRegs.set(Regs[i]);
}
}
}
void RISCVFrameLowering::processFunctionBeforeFrameFinalized(

View File

@ -967,6 +967,21 @@ SDValue RISCVTargetLowering::LowerFormalArguments(
}
MachineFunction &MF = DAG.getMachineFunction();
const Function &Func = MF.getFunction();
if (Func.hasFnAttribute("interrupt")) {
if (!Func.arg_empty())
report_fatal_error(
"Functions with the interrupt attribute cannot have arguments!");
StringRef Kind =
MF.getFunction().getFnAttribute("interrupt").getValueAsString();
if (!(Kind == "user" || Kind == "supervisor" || Kind == "machine"))
report_fatal_error(
"Function interrupt attribute argument not supported!");
}
EVT PtrVT = getPointerTy(DAG.getDataLayout());
MVT XLenVT = Subtarget.getXLenVT();
unsigned XLenInBytes = Subtarget.getXLen() / 8;
@ -1515,6 +1530,28 @@ RISCVTargetLowering::LowerReturn(SDValue Chain, CallingConv::ID CallConv,
RetOps.push_back(Glue);
}
// Interrupt service routines use different return instructions.
const Function &Func = DAG.getMachineFunction().getFunction();
if (Func.hasFnAttribute("interrupt")) {
if (!Func.getReturnType()->isVoidTy())
report_fatal_error(
"Functions with the interrupt attribute must have void return type!");
MachineFunction &MF = DAG.getMachineFunction();
StringRef Kind =
MF.getFunction().getFnAttribute("interrupt").getValueAsString();
unsigned RetOpc;
if (Kind == "user")
RetOpc = RISCVISD::URET_FLAG;
else if (Kind == "supervisor")
RetOpc = RISCVISD::SRET_FLAG;
else
RetOpc = RISCVISD::MRET_FLAG;
return DAG.getNode(RetOpc, DL, MVT::Other, RetOps);
}
return DAG.getNode(RISCVISD::RET_FLAG, DL, MVT::Other, RetOps);
}
@ -1524,6 +1561,12 @@ const char *RISCVTargetLowering::getTargetNodeName(unsigned Opcode) const {
break;
case RISCVISD::RET_FLAG:
return "RISCVISD::RET_FLAG";
case RISCVISD::URET_FLAG:
return "RISCVISD::URET_FLAG";
case RISCVISD::SRET_FLAG:
return "RISCVISD::SRET_FLAG";
case RISCVISD::MRET_FLAG:
return "RISCVISD::MRET_FLAG";
case RISCVISD::CALL:
return "RISCVISD::CALL";
case RISCVISD::SELECT_CC:

View File

@ -25,6 +25,9 @@ namespace RISCVISD {
enum NodeType : unsigned {
FIRST_NUMBER = ISD::BUILTIN_OP_END,
RET_FLAG,
URET_FLAG,
SRET_FLAG,
MRET_FLAG,
CALL,
SELECT_CC,
BuildPairF64,

View File

@ -118,7 +118,8 @@ void RISCVInstrInfo::storeRegToStackSlot(MachineBasicBlock &MBB,
unsigned Opcode;
if (RISCV::GPRRegClass.hasSubClassEq(RC))
Opcode = RISCV::SW;
Opcode = TRI->getRegSizeInBits(RISCV::GPRRegClass) == 32 ?
RISCV::SW : RISCV::SD;
else if (RISCV::FPR32RegClass.hasSubClassEq(RC))
Opcode = RISCV::FSW;
else if (RISCV::FPR64RegClass.hasSubClassEq(RC))
@ -144,7 +145,8 @@ void RISCVInstrInfo::loadRegFromStackSlot(MachineBasicBlock &MBB,
unsigned Opcode;
if (RISCV::GPRRegClass.hasSubClassEq(RC))
Opcode = RISCV::LW;
Opcode = TRI->getRegSizeInBits(RISCV::GPRRegClass) == 32 ?
RISCV::LW : RISCV::LD;
else if (RISCV::FPR32RegClass.hasSubClassEq(RC))
Opcode = RISCV::FLW;
else if (RISCV::FPR64RegClass.hasSubClassEq(RC))

View File

@ -36,6 +36,12 @@ def CallSeqEnd : SDNode<"ISD::CALLSEQ_END", SDT_RISCVCallSeqEnd,
[SDNPHasChain, SDNPOptInGlue, SDNPOutGlue]>;
def RetFlag : SDNode<"RISCVISD::RET_FLAG", SDTNone,
[SDNPHasChain, SDNPOptInGlue, SDNPVariadic]>;
def URetFlag : SDNode<"RISCVISD::URET_FLAG", SDTNone,
[SDNPHasChain, SDNPOptInGlue]>;
def SRetFlag : SDNode<"RISCVISD::SRET_FLAG", SDTNone,
[SDNPHasChain, SDNPOptInGlue]>;
def MRetFlag : SDNode<"RISCVISD::MRET_FLAG", SDTNone,
[SDNPHasChain, SDNPOptInGlue]>;
def SelectCC : SDNode<"RISCVISD::SELECT_CC", SDT_RISCVSelectCC,
[SDNPInGlue]>;
def Tail : SDNode<"RISCVISD::TAIL", SDT_RISCVCall,
@ -684,6 +690,10 @@ def PseudoCALL : Pseudo<(outs), (ins bare_symbol:$func),
def : Pat<(Call texternalsym:$func), (PseudoCALL texternalsym:$func)>;
def : Pat<(URetFlag), (URET X0, X0)>;
def : Pat<(SRetFlag), (SRET X0, X0)>;
def : Pat<(MRetFlag), (MRET X0, X0)>;
let isCall = 1, Defs = [X1] in
def PseudoCALLIndirect : Pseudo<(outs), (ins GPR:$rs1), [(Call GPR:$rs1)]>,
PseudoInstExpansion<(JALR X1, GPR:$rs1, 0)>;

View File

@ -33,6 +33,13 @@ RISCVRegisterInfo::RISCVRegisterInfo(unsigned HwMode)
const MCPhysReg *
RISCVRegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const {
if (MF->getFunction().hasFnAttribute("interrupt")) {
if (MF->getSubtarget<RISCVSubtarget>().hasStdExtD())
return CSR_XLEN_F64_Interrupt_SaveList;
if (MF->getSubtarget<RISCVSubtarget>().hasStdExtF())
return CSR_XLEN_F32_Interrupt_SaveList;
return CSR_Interrupt_SaveList;
}
return CSR_SaveList;
}
@ -108,7 +115,14 @@ unsigned RISCVRegisterInfo::getFrameRegister(const MachineFunction &MF) const {
}
const uint32_t *
RISCVRegisterInfo::getCallPreservedMask(const MachineFunction & /*MF*/,
RISCVRegisterInfo::getCallPreservedMask(const MachineFunction & MF,
CallingConv::ID /*CC*/) const {
if (MF.getFunction().hasFnAttribute("interrupt")) {
if (MF.getSubtarget<RISCVSubtarget>().hasStdExtD())
return CSR_XLEN_F64_Interrupt_RegMask;
if (MF.getSubtarget<RISCVSubtarget>().hasStdExtF())
return CSR_XLEN_F32_Interrupt_RegMask;
return CSR_Interrupt_RegMask;
}
return CSR_RegMask;
}

View File

@ -0,0 +1,11 @@
; RUN: not llc -mtriple riscv32-unknown-elf -o - %s \
; RUN: 2>&1 | FileCheck %s
; RUN: not llc -mtriple riscv64-unknown-elf -o - %s \
; RUN: 2>&1 | FileCheck %s
; CHECK: LLVM ERROR: Functions with the interrupt attribute cannot have arguments!
define i32 @isr_user(i8 %n) #0 {
ret i32 0
}
attributes #0 = { "interrupt"="user" }

View File

@ -0,0 +1,11 @@
; RUN: not llc -mtriple riscv32-unknown-elf -o - %s \
; RUN: 2>&1 | FileCheck %s
; RUN: not llc -mtriple riscv64-unknown-elf -o - %s \
; RUN: 2>&1 | FileCheck %s
; CHECK: LLVM ERROR: Function interrupt attribute argument not supported!
define void @isr_user() #0 {
ret void
}
attributes #0 = { "interrupt"="foo" }

View File

@ -0,0 +1,217 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
; RUN: llc -mtriple riscv32-unknown-elf -o - %s \
; RUN: 2>&1 | FileCheck %s -check-prefix CHECK-RV32
; RUN: llc -mtriple riscv32-unknown-elf -mattr=+f -o - %s \
; RUN: 2>&1 | FileCheck %s -check-prefix CHECK-RV32-F
; RUN: llc -mtriple riscv32-unknown-elf -mattr=+f,+d -o - %s \
; RUN: 2>&1 | FileCheck %s -check-prefix CHECK-RV32-FD
;
; TODO: Add RV64 tests when we can lower global addresses.
; Checking all registers that are used are being saved.
; This includes Caller (arguments and temps) and
; Callee saved registers.
;
; extern int a, b, c;
; __attribute__((interrupt)) void foo_no_call(void) {
; c = a + b;
; }
;
@a = external global i32
@b = external global i32
@c = external global i32
define void @foo_i32() #0 {
; CHECK-RV32-LABEL: foo_i32:
; CHECK-RV32: # %bb.0:
; CHECK-RV32-NEXT: addi sp, sp, -16
; CHECK-RV32-NEXT: sw a0, 12(sp)
; CHECK-RV32-NEXT: sw a1, 8(sp)
; CHECK-RV32-NEXT: lui a0, %hi(a)
; CHECK-RV32-NEXT: lw a0, %lo(a)(a0)
; CHECK-RV32-NEXT: lui a1, %hi(b)
; CHECK-RV32-NEXT: lw a1, %lo(b)(a1)
; CHECK-RV32-NEXT: add a0, a1, a0
; CHECK-RV32-NEXT: lui a1, %hi(c)
; CHECK-RV32-NEXT: sw a0, %lo(c)(a1)
; CHECK-RV32-NEXT: lw a1, 8(sp)
; CHECK-RV32-NEXT: lw a0, 12(sp)
; CHECK-RV32-NEXT: addi sp, sp, 16
; CHECK-RV32-NEXT: mret
;
%1 = load i32, i32* @a
%2 = load i32, i32* @b
%add = add nsw i32 %2, %1
store i32 %add, i32* @c
ret void
}
;
; Additionally check frame pointer and return address are properly saved.
;
define void @foo_fp_i32() #1 {
; CHECK-RV32-LABEL: foo_fp_i32:
; CHECK-RV32: # %bb.0:
; CHECK-RV32-NEXT: addi sp, sp, -16
; CHECK-RV32-NEXT: sw ra, 12(sp)
; CHECK-RV32-NEXT: sw s0, 8(sp)
; CHECK-RV32-NEXT: sw a0, 4(sp)
; CHECK-RV32-NEXT: sw a1, 0(sp)
; CHECK-RV32-NEXT: addi s0, sp, 16
; CHECK-RV32-NEXT: lui a0, %hi(a)
; CHECK-RV32-NEXT: lw a0, %lo(a)(a0)
; CHECK-RV32-NEXT: lui a1, %hi(b)
; CHECK-RV32-NEXT: lw a1, %lo(b)(a1)
; CHECK-RV32-NEXT: add a0, a1, a0
; CHECK-RV32-NEXT: lui a1, %hi(c)
; CHECK-RV32-NEXT: sw a0, %lo(c)(a1)
; CHECK-RV32-NEXT: lw a1, 0(sp)
; CHECK-RV32-NEXT: lw a0, 4(sp)
; CHECK-RV32-NEXT: lw s0, 8(sp)
; CHECK-RV32-NEXT: lw ra, 12(sp)
; CHECK-RV32-NEXT: addi sp, sp, 16
; CHECK-RV32-NEXT: mret
;
%1 = load i32, i32* @a
%2 = load i32, i32* @b
%add = add nsw i32 %2, %1
store i32 %add, i32* @c
ret void
}
@e = external global float
@f = external global float
@d = external global float
define void @foo_float() #0 {
; CHECK-RV32-F-LABEL: foo_float:
; CHECK-RV32-F: # %bb.0:
; CHECK-RV32-F-NEXT: addi sp, sp, -16
; CHECK-RV32-F-NEXT: sw a0, 12(sp)
; CHECK-RV32-F-NEXT: fsw ft0, 8(sp)
; CHECK-RV32-F-NEXT: fsw ft1, 4(sp)
; CHECK-RV32-F-NEXT: lui a0, %hi(f)
; CHECK-RV32-F-NEXT: flw ft0, %lo(f)(a0)
; CHECK-RV32-F-NEXT: lui a0, %hi(e)
; CHECK-RV32-F-NEXT: flw ft1, %lo(e)(a0)
; CHECK-RV32-F-NEXT: fadd.s ft0, ft1, ft0
; CHECK-RV32-F-NEXT: lui a0, %hi(d)
; CHECK-RV32-F-NEXT: fsw ft0, %lo(d)(a0)
; CHECK-RV32-F-NEXT: flw ft1, 4(sp)
; CHECK-RV32-F-NEXT: flw ft0, 8(sp)
; CHECK-RV32-F-NEXT: lw a0, 12(sp)
; CHECK-RV32-F-NEXT: addi sp, sp, 16
; CHECK-RV32-F-NEXT: mret
;
%1 = load float, float* @e
%2 = load float, float* @f
%add = fadd float %1, %2
store float %add, float* @d
ret void
}
;
; Additionally check frame pointer and return address are properly saved.
;
define void @foo_fp_float() #1 {
; CHECK-RV32-F-LABEL: foo_fp_float:
; CHECK-RV32-F: # %bb.0:
; CHECK-RV32-F-NEXT: addi sp, sp, -32
; CHECK-RV32-F-NEXT: sw ra, 28(sp)
; CHECK-RV32-F-NEXT: sw s0, 24(sp)
; CHECK-RV32-F-NEXT: sw a0, 20(sp)
; CHECK-RV32-F-NEXT: fsw ft0, 16(sp)
; CHECK-RV32-F-NEXT: fsw ft1, 12(sp)
; CHECK-RV32-F-NEXT: addi s0, sp, 32
; CHECK-RV32-F-NEXT: lui a0, %hi(f)
; CHECK-RV32-F-NEXT: flw ft0, %lo(f)(a0)
; CHECK-RV32-F-NEXT: lui a0, %hi(e)
; CHECK-RV32-F-NEXT: flw ft1, %lo(e)(a0)
; CHECK-RV32-F-NEXT: fadd.s ft0, ft1, ft0
; CHECK-RV32-F-NEXT: lui a0, %hi(d)
; CHECK-RV32-F-NEXT: fsw ft0, %lo(d)(a0)
; CHECK-RV32-F-NEXT: flw ft1, 12(sp)
; CHECK-RV32-F-NEXT: flw ft0, 16(sp)
; CHECK-RV32-F-NEXT: lw a0, 20(sp)
; CHECK-RV32-F-NEXT: lw s0, 24(sp)
; CHECK-RV32-F-NEXT: lw ra, 28(sp)
; CHECK-RV32-F-NEXT: addi sp, sp, 32
; CHECK-RV32-F-NEXT: mret
;
%1 = load float, float* @e
%2 = load float, float* @f
%add = fadd float %1, %2
store float %add, float* @d
ret void
}
@h = external global double
@i = external global double
@g = external global double
define void @foo_double() #0 {
; CHECK-RV32-FD-LABEL: foo_double:
; CHECK-RV32-FD: # %bb.0:
; CHECK-RV32-FD-NEXT: addi sp, sp, -32
; CHECK-RV32-FD-NEXT: sw a0, 28(sp)
; CHECK-RV32-FD-NEXT: fsd ft0, 16(sp)
; CHECK-RV32-FD-NEXT: fsd ft1, 8(sp)
; CHECK-RV32-FD-NEXT: lui a0, %hi(i)
; CHECK-RV32-FD-NEXT: fld ft0, %lo(i)(a0)
; CHECK-RV32-FD-NEXT: lui a0, %hi(h)
; CHECK-RV32-FD-NEXT: fld ft1, %lo(h)(a0)
; CHECK-RV32-FD-NEXT: fadd.d ft0, ft1, ft0
; CHECK-RV32-FD-NEXT: lui a0, %hi(g)
; CHECK-RV32-FD-NEXT: fsd ft0, %lo(g)(a0)
; CHECK-RV32-FD-NEXT: fld ft1, 8(sp)
; CHECK-RV32-FD-NEXT: fld ft0, 16(sp)
; CHECK-RV32-FD-NEXT: lw a0, 28(sp)
; CHECK-RV32-FD-NEXT: addi sp, sp, 32
; CHECK-RV32-FD-NEXT: mret
;
%1 = load double, double* @h
%2 = load double, double* @i
%add = fadd double %1, %2
store double %add, double* @g
ret void
}
;
; Additionally check frame pointer and return address are properly saved.
;
define void @foo_fp_double() #1 {
; CHECK-RV32-FD-LABEL: foo_fp_double:
; CHECK-RV32-FD: # %bb.0:
; CHECK-RV32-FD-NEXT: addi sp, sp, -32
; CHECK-RV32-FD-NEXT: sw ra, 28(sp)
; CHECK-RV32-FD-NEXT: sw s0, 24(sp)
; CHECK-RV32-FD-NEXT: sw a0, 20(sp)
; CHECK-RV32-FD-NEXT: fsd ft0, 8(sp)
; CHECK-RV32-FD-NEXT: fsd ft1, 0(sp)
; CHECK-RV32-FD-NEXT: addi s0, sp, 32
; CHECK-RV32-FD-NEXT: lui a0, %hi(i)
; CHECK-RV32-FD-NEXT: fld ft0, %lo(i)(a0)
; CHECK-RV32-FD-NEXT: lui a0, %hi(h)
; CHECK-RV32-FD-NEXT: fld ft1, %lo(h)(a0)
; CHECK-RV32-FD-NEXT: fadd.d ft0, ft1, ft0
; CHECK-RV32-FD-NEXT: lui a0, %hi(g)
; CHECK-RV32-FD-NEXT: fsd ft0, %lo(g)(a0)
; CHECK-RV32-FD-NEXT: fld ft1, 0(sp)
; CHECK-RV32-FD-NEXT: fld ft0, 8(sp)
; CHECK-RV32-FD-NEXT: lw a0, 20(sp)
; CHECK-RV32-FD-NEXT: lw s0, 24(sp)
; CHECK-RV32-FD-NEXT: lw ra, 28(sp)
; CHECK-RV32-FD-NEXT: addi sp, sp, 32
; CHECK-RV32-FD-NEXT: mret
;
%1 = load double, double* @h
%2 = load double, double* @i
%add = fadd double %1, %2
store double %add, double* @g
ret void
}
attributes #0 = { "interrupt"="machine" }
attributes #1 = { "interrupt"="machine" "no-frame-pointer-elim"="true" }

View File

@ -0,0 +1,12 @@
; RUN: not llc -mtriple riscv32-unknown-elf -o - %s \
; RUN: 2>&1 | FileCheck %s
; RUN: not llc -mtriple riscv64-unknown-elf -o - %s \
; RUN: 2>&1 | FileCheck %s
; CHECK: LLVM ERROR: Functions with the interrupt attribute must have void return type!
define i32 @isr1_user() #0 {
ret i32 0
}
attributes #0 = { "interrupt"="user" }

File diff suppressed because it is too large Load Diff