Index: clang/include/clang/Basic/Attr.td =================================================================== --- clang/include/clang/Basic/Attr.td +++ clang/include/clang/Basic/Attr.td @@ -1612,8 +1612,14 @@ let Spellings = [GCC<"interrupt">]; let Subjects = SubjectList<[Function]>; let Args = [EnumArgument<"Interrupt", "InterruptType", - ["user", "supervisor", "machine"], - ["user", "supervisor", "machine"], + ["user", "supervisor", "machine", + "SiFive-CLIC-preemptible", + "SiFive-CLIC-stack-swap", + "SiFive-CLIC-preemptible-stack-swap"], + ["user", "supervisor", "machine", + "SiFiveCLICPreemptible", + "SiFiveCLICStackSwap", + "SiFiveCLICPreemptibleStackSwap"], 1>]; let ParseKind = "Interrupt"; let Documentation = [RISCVInterruptDocs]; Index: clang/include/clang/Basic/AttrDocs.td =================================================================== --- clang/include/clang/Basic/AttrDocs.td +++ clang/include/clang/Basic/AttrDocs.td @@ -1831,6 +1831,10 @@ Permissible values for this parameter are ``user``, ``supervisor``, and ``machine``. If there is no parameter, then it defaults to machine. +In addition, SiFive-specific interrupt parameters are supported. +They include ``SiFive-CLIC-preemptible'' and ``SiFive-CLIC-stack-swap'' +which expect interrupt handler type to be ``machine'' mode. + Repeated interrupt attribute on the same declaration will cause a warning to be emitted. In case of repeated declarations, the last one prevails. @@ -1839,6 +1843,7 @@ https://riscv.org/specifications/privileged-isa/ The RISC-V Instruction Set Manual Volume II: Privileged Architecture Version 1.10. +https://github.com/riscv/riscv-fast-interrupt/ }]; } Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -293,6 +293,8 @@ "%select{MIPS|MSP430|RISC-V}0 'interrupt' attribute only applies to " "functions that have %select{no parameters|a 'void' return type}1">, InGroup; +def error_riscv_machine_multi_mode_interrupt_attribute : Error< + "RISC-V %0 'interrupt' function must be machine mode">; def warn_riscv_repeated_interrupt_attribute : Warning< "repeated RISC-V 'interrupt' attribute">, InGroup; def note_riscv_repeated_interrupt_attribute : Note< Index: clang/lib/CodeGen/TargetInfo.cpp =================================================================== --- clang/lib/CodeGen/TargetInfo.cpp +++ clang/lib/CodeGen/TargetInfo.cpp @@ -10397,6 +10397,12 @@ case RISCVInterruptAttr::user: Kind = "user"; break; case RISCVInterruptAttr::supervisor: Kind = "supervisor"; break; case RISCVInterruptAttr::machine: Kind = "machine"; break; + case RISCVInterruptAttr::SiFiveCLICPreemptible: + Kind = "SiFive-CLIC-preemptible"; break; + case RISCVInterruptAttr::SiFiveCLICStackSwap: + Kind = "SiFive-CLIC-stack-swap"; break; + case RISCVInterruptAttr::SiFiveCLICPreemptibleStackSwap: + Kind = "SiFive-CLIC-preemptible-stack-swap"; break; } auto *Fn = cast(GV); Index: clang/lib/Sema/SemaDeclAttr.cpp =================================================================== --- clang/lib/Sema/SemaDeclAttr.cpp +++ clang/lib/Sema/SemaDeclAttr.cpp @@ -5921,8 +5921,127 @@ FD->addAttr(::new (S.Context) WebAssemblyImportNameAttr(S.Context, AL, Str)); } +static void handleRISCVVendorInterruptAttr(Sema &S, Decl *D, + const ParsedAttr &AL) { + + // Check the attribute argument. Expecting exactly 2. + if (!checkAttributeAtLeastNumArgs(S, AL, 2) || + !checkAttributeAtMostNumArgs(S, AL, 2)) + return; + + std::vector Modes; + SourceLocation ArgLoc; + + for (unsigned I = 0, E = AL.getNumArgs(); I != E; ++I) { + StringRef Str; + if (!S.checkStringLiteralArgumentAttr(AL, I, Str, &ArgLoc)) + return; + + RISCVInterruptAttr::InterruptType Kind; + if (!RISCVInterruptAttr::ConvertStrToInterruptType(Str, Kind)) { + S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported) + << AL << Str << ArgLoc; + return; + } + + Modes.push_back(Kind); + } + + auto AllowDoubleMode = + [](const std::vector Modes, + RISCVInterruptAttr::InterruptType &Kind) { + + enum Mask { + Machine = 1 << 0, + SiFiveCLICStackSwap = 1 << 1, + SiFiveCLICPreemptible = 1 << 2, + Invalid = 1 << 3 + }; + + Mask M = static_cast(0); + // Default mode is Machine. + Kind = RISCVInterruptAttr::InterruptType::machine; + + for (unsigned I = 0, E = Modes.size(); I != E; ++I) { + switch(Modes[I]) { + default: + M = static_cast(M | Invalid); break; + case RISCVInterruptAttr::InterruptType::machine: + M = static_cast(M | Machine); break; + case RISCVInterruptAttr::InterruptType::SiFiveCLICStackSwap: + M = static_cast(M | SiFiveCLICStackSwap); break; + case RISCVInterruptAttr::InterruptType::SiFiveCLICPreemptible: + M = static_cast(M | SiFiveCLICPreemptible); break; + } + } + + if (M & SiFiveCLICStackSwap && M & SiFiveCLICPreemptible) + Kind = RISCVInterruptAttr::InterruptType::SiFiveCLICPreemptibleStackSwap; + + else if (M & SiFiveCLICStackSwap) + Kind = RISCVInterruptAttr::InterruptType::SiFiveCLICStackSwap; + + else if (M & SiFiveCLICPreemptible) + Kind = RISCVInterruptAttr::InterruptType::SiFiveCLICPreemptible; + + else + // Double-mode requires at least one SiFive interrupt to be set, + // invalidate mask if the SiFive interrupts are missing. + M = static_cast(M | Invalid); + + return !(M & Invalid); + }; + + // Only allow double mode for SiFive's interrupts. + // SiFive interrupts can be set together or with macine mode. + RISCVInterruptAttr::InterruptType Kind; + if (!AllowDoubleMode(Modes, Kind)) { + + if (Kind == RISCVInterruptAttr::InterruptType::SiFiveCLICPreemptible || + Kind == RISCVInterruptAttr::InterruptType::SiFiveCLICStackSwap || + Kind == RISCVInterruptAttr::InterruptType::SiFiveCLICPreemptibleStackSwap) { + + S.Diag(AL.getLoc(), + diag::error_riscv_machine_multi_mode_interrupt_attribute) + << RISCVInterruptAttr::ConvertInterruptTypeToStr(Kind); + + } else + S.Diag(AL.getLoc(), diag::err_attribute_too_many_arguments) << AL << 2; + + return; + } + + // Semantic checks for a function with the 'interrupt' attribute: + // - Must be a function. + // - Must have no parameters. + // - Must have the 'void' return type. + // - The attribute itself must either have no argument or one of the + // valid interrupt types, see [RISCVInterruptDocs]. + + if (D->getFunctionType() == nullptr) { + S.Diag(D->getLocation(), diag::warn_attribute_wrong_decl_type) + << "'interrupt'" << ExpectedFunction; + return; + } + + if (hasFunctionProto(D) && getFunctionOrMethodNumParams(D) != 0) { + S.Diag(D->getLocation(), diag::warn_interrupt_attribute_invalid) + << /*RISC-V*/ 2 << 0; + return; + } + + if (!getFunctionOrMethodResultType(D)->isVoidType()) { + S.Diag(D->getLocation(), diag::warn_interrupt_attribute_invalid) + << /*RISC-V*/ 2 << 1; + return; + } + + D->addAttr(::new (S.Context) RISCVInterruptAttr(S.Context, AL, Kind)); +} + static void handleRISCVInterruptAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + // Warn about repeated attributes. if (const auto *A = D->getAttr()) { S.Diag(AL.getRange().getBegin(), @@ -5931,6 +6050,9 @@ return; } + if (AL.getNumArgs() >= 2) + return handleRISCVVendorInterruptAttr(S, D, AL); + // Check the attribute argument. Argument is optional. if (!checkAttributeAtMostNumArgs(S, AL, 1)) return; Index: clang/test/Sema/riscv-interrupt-attr.c =================================================================== --- clang/test/Sema/riscv-interrupt-attr.c +++ clang/test/Sema/riscv-interrupt-attr.c @@ -32,7 +32,8 @@ __attribute__((interrupt("USER"))) void foo1(void) {} // expected-warning {{'interrupt' attribute argument not supported: USER}} -__attribute__((interrupt("user", 1))) void foo2(void) {} // expected-error {{'interrupt' attribute takes no more than 1 argument}} +// To handle vendor-specific interrupts, allow more than one attribute +__attribute__((interrupt("user", 1))) void foo2(void) {} // expected-error {{'interrupt' attribute requires a string}} __attribute__((interrupt)) int foo3(void) {return 0;} // expected-warning {{RISC-V 'interrupt' attribute only applies to functions that have a 'void' return type}} Index: clang/test/Sema/riscv-sifive-interrupt-attr.c =================================================================== --- /dev/null +++ clang/test/Sema/riscv-sifive-interrupt-attr.c @@ -0,0 +1,136 @@ +// RUN: %clang_cc1 -triple riscv32-unknown-elf -emit-llvm -DCHECK_IR < %s| FileCheck %s +// RUN: %clang_cc1 -triple riscv64-unknown-elf -emit-llvm -DCHECK_IR < %s| FileCheck %s +// RUN: %clang_cc1 %s -triple riscv32-unknown-elf -verify -fsyntax-only +// RUN: %clang_cc1 %s -triple riscv64-unknown-elf -verify -fsyntax-only + +#if defined(CHECK_IR) +// CHECK-LABEL: @foo_stack_swap() #0 +// CHECK: ret void +__attribute__((interrupt("SiFive-CLIC-stack-swap"))) void foo_stack_swap(void) {} + +// CHECK-LABEL: @foo_preemptible() #1 +// CHECK: ret void +__attribute__((interrupt("SiFive-CLIC-preemptible"))) void foo_preemptible(void) {} + +// CHECK-LABEL: @foo_stack_swap_preemptible() #2 +// CHECK: ret void +__attribute__((interrupt("SiFive-CLIC-stack-swap", "SiFive-CLIC-preemptible"))) +void foo_stack_swap_preemptible(void) {} + +// CHECK-LABEL: @foo_preemptible_stack_swap() #2 +// CHECK: ret void +__attribute__((interrupt("SiFive-CLIC-preemptible", "SiFive-CLIC-stack-swap"))) +void foo_preemptible_stack_swap(void) {} + +// CHECK-LABEL: @foo_stack_swap_repeat() #0 +// CHECK: ret void +__attribute__((interrupt("SiFive-CLIC-stack-swap", "SiFive-CLIC-stack-swap"))) +void foo_stack_swap_repeat(void) {} + +// CHECK-LABEL: @foo_preemptible_repeat() #1 +// CHECK: ret void +__attribute__((interrupt("SiFive-CLIC-preemptible", "SiFive-CLIC-preemptible"))) +void foo_preemptible_repeat(void) {} + +// CHECK-LABEL: @foo_machine_stack_swap() #0 +// CHECK: ret void +__attribute__((interrupt("machine", "SiFive-CLIC-stack-swap"))) +void foo_machine_stack_swap(void) {} + +// CHECK-LABEL: @foo_stack_swap_machine() #0 +// CHECK: ret void +__attribute__((interrupt("SiFive-CLIC-stack-swap", "machine"))) +void foo_stack_swap_machine(void) {} + +// CHECK-LABEL: @foo_preemptible_machine() #1 +// CHECK: ret void +__attribute__((interrupt("SiFive-CLIC-preemptible", "machine"))) +void foo_preemptible_machine(void) {} + +// CHECK-LABEL: @foo_machine_preemptible() #1 +// CHECK: ret void +__attribute__((interrupt("machine", "SiFive-CLIC-preemptible"))) +void foo_machine_preemptible(void) {} + + +// CHECK: attributes #0 +// CHECK: "interrupt"="SiFive-CLIC-stack-swap" +// CHECK: attributes #1 +// CHECK: "interrupt"="SiFive-CLIC-preemptible" +// CHECK: attributes #2 +// CHECK: "interrupt"="SiFive-CLIC-preemptible-stack-swap" +#else +struct a { int b; }; + +struct a test __attribute__((interrupt)); // expected-warning {{'interrupt' attribute only applies to functions}} + +__attribute__((interrupt("USER"))) void foo1(void) {} // expected-warning {{'interrupt' attribute argument not supported: USER}} + +__attribute__((interrupt("user", 1))) void foo2(void) {} // expected-error {{'interrupt' attribute requires a string}} + +__attribute__((interrupt)) int foo3(void) {return 0;} // expected-warning {{RISC-V 'interrupt' attribute only applies to functions that have a 'void' return type}} + +__attribute__((interrupt())) void foo4(); +__attribute__((interrupt())) void foo4() {}; + +__attribute__((interrupt())) void foo5(int a) {} // expected-warning {{RISC-V 'interrupt' attribute only applies to functions that have no parameters}} + +__attribute__((interrupt("user"), interrupt("supervisor"))) void foo6(void) {} // expected-warning {{repeated RISC-V 'interrupt' attribute}} \ + // expected-note {{repeated RISC-V 'interrupt' attribute is here}} + +__attribute__((interrupt, interrupt)) void foo7(void) {} // expected-warning {{repeated RISC-V 'interrupt' attribute}} \ + // expected-note {{repeated RISC-V 'interrupt' attribute is here}} + +__attribute__((interrupt(""))) void foo8(void) {} // expected-warning {{'interrupt' attribute argument not supported}} + +__attribute__((interrupt("user"))) void foo9(void); +__attribute__((interrupt("supervisor"))) void foo9(void); +__attribute__((interrupt("machine"))) void foo9(void); + +__attribute__((interrupt("user"))) void foo10(void) {} +__attribute__((interrupt("supervisor"))) void foo11(void) {} +__attribute__((interrupt("machine"))) void foo12(void) {} +__attribute__((interrupt())) void foo13(void) {} +__attribute__((interrupt)) void foo14(void) {} + +__attribute__((interrupt("SiFive-CLIC-stack-swap"))) void foo15(void); +__attribute__((interrupt("SiFive-CLIC-stack-swap", "SiFive-CLIC-stack-swap"))) void foo15(void); +__attribute__((interrupt("SiFive-CLIC-stack-swap", "machine"))) void foo15(void); +__attribute__((interrupt("machine", "SiFive-CLIC-stack-swap"))) void foo15(void); + +__attribute__((interrupt("SiFive-CLIC-preemptible"))) void foo15(void); +__attribute__((interrupt("SiFive-CLIC-preemptible", "SiFive-CLIC-preemptible"))) void foo15(void); +__attribute__((interrupt("SiFive-CLIC-preemptible", "machine"))) void foo15(void); +__attribute__((interrupt("machine", "SiFive-CLIC-preemptible"))) void foo15(void); + +__attribute__((interrupt("SiFive-CLIC-stack-swap"))) void foo16(void) {} +__attribute__((interrupt("SiFive-CLIC-stack-swap", "SiFive-CLIC-stack-swap"))) void foo17(void) {} +__attribute__((interrupt("SiFive-CLIC-stack-swap", "machine"))) void foo18(void) {} +__attribute__((interrupt("machine", "SiFive-CLIC-stack-swap"))) void foo19(void) {} + +__attribute__((interrupt("SiFive-CLIC-preemptible"))) void foo20(void) {} +__attribute__((interrupt("SiFive-CLIC-preemptible", "SiFive-CLIC-preemptible"))) void foo21(void) {} +__attribute__((interrupt("SiFive-CLIC-preemptible", "machine"))) void foo22(void) {} +__attribute__((interrupt("machine", "SiFive-CLIC-preemptible"))) void foo23(void) {} + + +__attribute__((interrupt("machine", "machine", "SiFive-CLIC-preemptible"))) void foo24(void) {} // expected-error {{'interrupt' attribute takes no more than 2 arguments}} + +__attribute__((interrupt("user", "SiFive-CLIC-preemptible"))) void foo25(void) {} // expected-error {{RISC-V SiFive-CLIC-preemptible 'interrupt' function must be machine mode}} + +__attribute__((interrupt("SiFive-CLIC-stack-swap", "user"))) void foo26(void) {} // expected-error {{RISC-V SiFive-CLIC-stack-swap 'interrupt' function must be machine mode}} + +__attribute__((interrupt("SiFive-CLIC-preemptible", "supervisor"))) void foo27(void) {} // expected-error {{RISC-V SiFive-CLIC-preemptible 'interrupt' function must be machine mode}} + +__attribute__((interrupt("supervisor", "SiFive-CLIC-stack-swap"))) void foo28(void) {} // expected-error {{RISC-V SiFive-CLIC-stack-swap 'interrupt' function must be machine mode}} + +__attribute__((interrupt("SiFive-CLIC-stack-swap", 1))) void foo29(void) {} // expected-error {{'interrupt' attribute requires a string}} + +__attribute__((interrupt(1, "SiFive-CLIC-stack-swap"))) void foo30(void) {} // expected-error {{'interrupt' attribute requires a string}} + +__attribute__((interrupt("SiFive-CLIC-stack-swap", "foo"))) void foo31(void) {} // expected-warning {{'interrupt' attribute argument not supported: foo}} + +__attribute__((interrupt("foo", "SiFive-CLIC-stack-swap"))) void foo32(void) {} // expected-warning {{'interrupt' attribute argument not supported: foo}} + +__attribute__((interrupt("machine", "machine"))) void foo33(void) {} // expected-error {{'interrupt' attribute takes no more than 2 arguments}} +#endif Index: llvm/lib/Target/RISCV/RISCVFrameLowering.cpp =================================================================== --- llvm/lib/Target/RISCV/RISCVFrameLowering.cpp +++ llvm/lib/Target/RISCV/RISCVFrameLowering.cpp @@ -117,6 +117,151 @@ return RestoreLibCalls[LibCallID]; } +static bool isPreemptibleInterrupt( + const MachineFunction &MF) { + + const Function &Func = MF.getFunction(); + if (!Func.hasFnAttribute("interrupt")) + return false; + + const TargetFrameLowering *TFI = + MF.getSubtarget().getFrameLowering(); + + if (TFI->hasFP(MF)) + return false; + + StringRef Kind = + MF.getFunction().getFnAttribute("interrupt").getValueAsString(); + + if (Kind == "SiFive-CLIC-preemptible") + return true; + + if (Kind == "SiFive-CLIC-preemptible-stack-swap") + return true; + + return false; +} + +static bool isSwapStackInterrupt( + const MachineFunction &MF, bool isPreemptible = 0) { + + const Function &Func = MF.getFunction(); + if (!Func.hasFnAttribute("interrupt")) + return false; + + StringRef Kind = + MF.getFunction().getFnAttribute("interrupt").getValueAsString(); + + if (Kind == "SiFive-CLIC-stack-swap") + return true; + + if (Kind == "SiFive-CLIC-preemptible-stack-swap") + return true; + + return false; +} + +// Returns the register used to hold the stack pointer. +static Register getSPReg(const RISCVSubtarget &STI) { return RISCV::X2; } + +static void emitInterruptPrologue( + MachineFunction &MF, MachineBasicBlock &MBB, + MachineBasicBlock::iterator MBBI) { + const auto &STI = MF.getSubtarget(); + const RISCVInstrInfo *TII = STI.getInstrInfo(); + DebugLoc DL = MBBI != MBB.end() ? MBBI->getDebugLoc() : DebugLoc(); + Register SPReg = getSPReg(STI); + + // In the prologue, the mepc and mcause registers are saved, and + // interrupts are enabled. + if (isPreemptibleInterrupt(MF)) { + + auto *RVFI = MF.getInfo(); + + TII->storeRegToStackSlot(MBB, MBBI, RISCV::X8, /* IsKill */ false, + RVFI->getISRRegFI(0), &RISCV::GPRRegClass, STI.getRegisterInfo()); + + TII->storeRegToStackSlot(MBB, MBBI, RISCV::X9, /* IsKill */ false, + RVFI->getISRRegFI(1), &RISCV::GPRRegClass, STI.getRegisterInfo()); + + BuildMI(MBB, MBBI, DL, TII->get(RISCV::CSRRS)) + .addReg(RISCV::X8, RegState::Define) + .addImm(RISCVSysReg::lookupSysRegByName("MCAUSE")->Encoding) + .addReg(RISCV::X0); + + BuildMI(MBB, MBBI, DL, TII->get(RISCV::CSRRS)) + .addReg(RISCV::X9, RegState::Define) + .addImm(RISCVSysReg::lookupSysRegByName("MEPC")->Encoding) + .addReg(RISCV::X0); + + // Enable interrupts. + BuildMI(MBB, MBBI, DL, TII->get(RISCV::CSRRSI)) + .addReg(RISCV::X0) + .addImm(RISCVSysReg::lookupSysRegByName("MSTATUS")->Encoding) + .addImm(8); + } + + // The stack pointer will be swapped with the mscratch register in + // the prologue // before the first use of the stack pointer. + if (isSwapStackInterrupt(MF)) { + BuildMI(MBB, MBB.begin(), DL, TII->get(RISCV::CSRRW)) + .addReg(SPReg, RegState::Define) + .addImm(RISCVSysReg::lookupSysRegByName("MSCRATCHCSW")->Encoding) + .addReg(SPReg); + } +} + +static void emitInterruptEpilogue( + MachineFunction &MF, MachineBasicBlock &MBB) { + const auto &STI = MF.getSubtarget(); + const RISCVInstrInfo *TII = STI.getInstrInfo(); + Register SPReg = getSPReg(STI); + + // In the epilogue, interrupts are disabled, and the mepc and + // mcause registers are restored, before the last use of the + // stack pointer. + if (isPreemptibleInterrupt(MF)) { + MachineBasicBlock::iterator MBBI = MBB.getLastNonDebugInstr(); + MBBI = std::prev(MBBI); + DebugLoc DL = MBBI->getDebugLoc(); + // Disable interrupts. + // MBB.addLiveIn(RISCVSysReg::MSTATUS); + BuildMI(MBB, MBBI, DL, TII->get(RISCV::CSRRCI)) + .addReg(RISCV::X0) + .addImm(RISCVSysReg::lookupSysRegByName("MSTATUS")->Encoding) + .addImm(8); + + BuildMI(MBB, MBBI, DL, TII->get(RISCV::CSRRW)) + .addReg(RISCV::X0) + .addImm(RISCVSysReg::lookupSysRegByName("MEPC")->Encoding) + .addReg(RISCV::X9); + + BuildMI(MBB, MBBI, DL, TII->get(RISCV::CSRRW)) + .addReg(RISCV::X0) + .addImm(RISCVSysReg::lookupSysRegByName("MCAUSE")->Encoding) + .addReg(RISCV::X8); + + auto *RVFI = MF.getInfo(); + TII->loadRegFromStackSlot(MBB, MBBI, RISCV::X9, + RVFI->getISRRegFI(1), &RISCV::GPRRegClass, STI.getRegisterInfo()); + + TII->loadRegFromStackSlot(MBB, MBBI, RISCV::X8, + RVFI->getISRRegFI(0), &RISCV::GPRRegClass, STI.getRegisterInfo()); + } + + // The stack pointer will be swapped with the mscratch register in + // the epilogue after the last use of the stack pointer. + if (isSwapStackInterrupt(MF)) { + MachineBasicBlock::iterator MBBI = MBB.getLastNonDebugInstr(); + DebugLoc DL = MBBI->getDebugLoc(); + BuildMI(MBB, MBBI, DL, TII->get(RISCV::CSRRW)) + .addReg(SPReg, RegState::Define) + .addImm(RISCVSysReg::lookupSysRegByName("MSCRATCHCSW")->Encoding) + .addReg(SPReg); + } +} + + bool RISCVFrameLowering::hasFP(const MachineFunction &MF) const { const TargetRegisterInfo *RegInfo = MF.getSubtarget().getRegisterInfo(); @@ -196,9 +341,6 @@ // Returns the register used to hold the frame pointer. static Register getFPReg(const RISCVSubtarget &STI) { return RISCV::X8; } -// 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; @@ -265,8 +407,11 @@ uint64_t RealStackSize = StackSize + RVFI->getLibCallStackSize(); // Early exit if there is no need to allocate on the stack - if (RealStackSize == 0 && !MFI.adjustsStack()) + if (RealStackSize == 0 && !MFI.adjustsStack()) { + // Support for vendor specific interrupts (SiFive's) + emitInterruptPrologue(MF, MBB, MBBI); return; + } // If the stack pointer has been marked as reserved, then produce an error if // the frame requires stack allocation @@ -290,6 +435,9 @@ BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION)) .addCFIIndex(CFIIndex); + // Support for vendor specific interrupts (SiFive's) + emitInterruptPrologue(MF, MBB, MBBI); + const auto &CSI = MFI.getCalleeSavedInfo(); // The frame pointer is callee-saved, and code has been generated for us to @@ -457,6 +605,9 @@ // Deallocate stack adjustReg(MBB, MBBI, DL, SPReg, SPReg, StackSize, MachineInstr::FrameDestroy); + + // Support for vendor specific interrupts (SiFive's). + emitInterruptEpilogue(MF, MBB); } int RISCVFrameLowering::getFrameIndexReference(const MachineFunction &MF, @@ -559,6 +710,11 @@ SavedRegs.set(Regs[i]); } } + + // Support vendor specific interrupts (SiFive's). + if (isPreemptibleInterrupt(MF)) { + MF.getInfo()->createISRRegFI(); + } } void RISCVFrameLowering::processFunctionBeforeFrameFinalized( Index: llvm/lib/Target/RISCV/RISCVISelLowering.cpp =================================================================== --- llvm/lib/Target/RISCV/RISCVISelLowering.cpp +++ llvm/lib/Target/RISCV/RISCVISelLowering.cpp @@ -1926,9 +1926,19 @@ StringRef Kind = MF.getFunction().getFnAttribute("interrupt").getValueAsString(); - if (!(Kind == "user" || Kind == "supervisor" || Kind == "machine")) + // Support vendor-specific interrupts (SiFive's). + if (!(Kind == "user" || Kind == "supervisor" || Kind == "machine" || + Kind == "SiFive-CLIC-preemptible" || + Kind == "SiFive-CLIC-stack-swap" || + Kind == "SiFive-CLIC-preemptible-stack-swap")) report_fatal_error( "Function interrupt attribute argument not supported!"); + + const TargetFrameLowering *TFI = Subtarget.getFrameLowering(); + if ((Kind == "SiFive-CLIC-preemptible" || + Kind == "SiFive-CLIC-preemptible-stack-swap") && TFI->hasFP(MF)) + report_fatal_error("SiFive CLIC preemptible 'interrupt' function " + "cannot use a frame pointer!"); } EVT PtrVT = getPointerTy(DAG.getDataLayout()); Index: llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h =================================================================== --- llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h +++ llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h @@ -34,6 +34,10 @@ /// Size of any opaque stack adjustment due to save/restore libcalls. unsigned LibCallStackSize = 0; + /// QC-specific: Support for SiFive's interrupts. + /// Frame objecst for spilling MEPC, MCAUSE. + int ISRDataRegFI[2]; + public: RISCVMachineFunctionInfo(MachineFunction &MF) : MF(MF) {} @@ -58,6 +62,21 @@ return MF.getSubtarget().enableSaveRestore() && VarArgsSaveSize == 0 && !MF.getFrameInfo().hasTailCall(); } + + /// QC-specific: Support for SiFive's interrupts. + void createISRRegFI() { + + // Create frame objecst for spilling MEPC, MCAUSE. + const TargetRegisterClass &RC = RISCV::GPRRegClass; + const TargetRegisterInfo &TRI = + *MF.getSubtarget().getRegisterInfo(); + + for (int I = 0; I < 2; ++I) + ISRDataRegFI[I] = MF.getFrameInfo().CreateStackObject( + TRI.getSpillSize(RC), TRI.getSpillAlignment(RC), false); + } + + int getISRRegFI(unsigned Reg) const {return ISRDataRegFI[Reg];} }; } // end namespace llvm Index: llvm/test/CodeGen/RISCV/sifive-interrupt-attr.ll =================================================================== --- /dev/null +++ llvm/test/CodeGen/RISCV/sifive-interrupt-attr.ll @@ -0,0 +1,375 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py +; RUN: llc -mtriple riscv32-unknown-elf -mattr=+experimental-clic -o - %s \ +; RUN: 2>&1 | FileCheck %s -check-prefix CHECK -check-prefix CHECK-RV32 + +; Check the stack pointer is swapped with mscratch register +; for SiFive-CLIC-stack-swap interrupt type, and check for +; special return instruction mret. + +define void @foo_stack_swap() #0 { +; CHECK-LABEL: foo_stack_swap: +; CHECK: # %bb.0: +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: mret + ret void +} + +; Additionally check frame pointer and return address are properly saved. +define void @foo_stack_swap_fp() #1 { +; CHECK-LABEL: foo_stack_swap_fp: +; CHECK: # %bb.0: +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: addi sp, sp, -16 +; CHECK-NEXT: sw ra, 12(sp) +; CHECK-NEXT: sw s0, 8(sp) +; CHECK-NEXT: addi s0, sp, 16 +; CHECK-NEXT: lw s0, 8(sp) +; CHECK-NEXT: lw ra, 12(sp) +; CHECK-NEXT: addi sp, sp, 16 +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: mret + ret void +} + +; Check mpec and mcause registers are saved +; for SiFive-CLIC-preemptible interrupt type, and check for +; special return instruction mret. + +define void @foo_preemptible() #2 { +; CHECK-LABEL: foo_preemptible: +; CHECK: # %bb.0: +; CHECK-NEXT: addi sp, sp, -16 +; CHECK-NEXT: sw s0, 12(sp) +; CHECK-NEXT: sw s1, 8(sp) +; CHECK-NEXT: csrr s0, mcause +; CHECK-NEXT: csrr s1, mepc +; CHECK-NEXT: csrsi mstatus, 8 +; CHECK-NEXT: csrci mstatus, 8 +; CHECK-NEXT: csrw mepc, s1 +; CHECK-NEXT: csrw mcause, s0 +; CHECK-NEXT: lw s1, 8(sp) +; CHECK-NEXT: lw s0, 12(sp) +; CHECK-NEXT: addi sp, sp, 16 +; CHECK-NEXT: mret + ret void +} + +; Check the stack pointer is swapped with mscratch register +; for SiFive-CLIC-stack-swap interrupt type, and +; check mpec and mcause registers are saved +; for SiFive-CLIC-preemptible interrupt type, and +; check for special return instruction mret. + +define void @foo_preemptible_stack_swap() #3 { +; CHECK-LABEL: foo_preemptible_stack_swap: +; CHECK: # %bb.0: +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: addi sp, sp, -16 +; CHECK-NEXT: sw s0, 12(sp) +; CHECK-NEXT: sw s1, 8(sp) +; CHECK-NEXT: csrr s0, mcause +; CHECK-NEXT: csrr s1, mepc +; CHECK-NEXT: csrsi mstatus, 8 +; CHECK-NEXT: csrci mstatus, 8 +; CHECK-NEXT: csrw mepc, s1 +; CHECK-NEXT: csrw mcause, s0 +; CHECK-NEXT: lw s1, 8(sp) +; CHECK-NEXT: lw s0, 12(sp) +; CHECK-NEXT: addi sp, sp, 16 +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: mret + ret void +} + + +; Repeat the above checks but also check +; Caller saved registers (arguments and temps) +; are all saved when the handler calls another function. + +declare dso_local void @otherfoo(...) + +define void @test_stack_swap_with_call() #0 { +; CHECK-LABEL: test_stack_swap_with_call: +; CHECK: # %bb.0: +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: addi sp, sp, -64 +; CHECK-NEXT: sw ra, 60(sp) +; CHECK-NEXT: sw t0, 56(sp) +; CHECK-NEXT: sw t1, 52(sp) +; CHECK-NEXT: sw t2, 48(sp) +; CHECK-NEXT: sw a0, 44(sp) +; CHECK-NEXT: sw a1, 40(sp) +; CHECK-NEXT: sw a2, 36(sp) +; CHECK-NEXT: sw a3, 32(sp) +; CHECK-NEXT: sw a4, 28(sp) +; CHECK-NEXT: sw a5, 24(sp) +; CHECK-NEXT: sw a6, 20(sp) +; CHECK-NEXT: sw a7, 16(sp) +; CHECK-NEXT: sw t3, 12(sp) +; CHECK-NEXT: sw t4, 8(sp) +; CHECK-NEXT: sw t5, 4(sp) +; CHECK-NEXT: sw t6, 0(sp) +; CHECK-NEXT: call otherfoo +; CHECK-NEXT: lw t6, 0(sp) +; CHECK-NEXT: lw t5, 4(sp) +; CHECK-NEXT: lw t4, 8(sp) +; CHECK-NEXT: lw t3, 12(sp) +; CHECK-NEXT: lw a7, 16(sp) +; CHECK-NEXT: lw a6, 20(sp) +; CHECK-NEXT: lw a5, 24(sp) +; CHECK-NEXT: lw a4, 28(sp) +; CHECK-NEXT: lw a3, 32(sp) +; CHECK-NEXT: lw a2, 36(sp) +; CHECK-NEXT: lw a1, 40(sp) +; CHECK-NEXT: lw a0, 44(sp) +; CHECK-NEXT: lw t2, 48(sp) +; CHECK-NEXT: lw t1, 52(sp) +; CHECK-NEXT: lw t0, 56(sp) +; CHECK-NEXT: lw ra, 60(sp) +; CHECK-NEXT: addi sp, sp, 64 +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: mret + call void bitcast (void (...)* @otherfoo to void ()*)() + ret void +} + +; Additionally check frame pointer and return address are properly saved. +define void @foo_stack_swap_fp_with_call() #1 { +; CHECK-LABEL: foo_stack_swap_fp_with_call: +; CHECK: # %bb.0: +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: addi sp, sp, -80 +; CHECK-NEXT: sw ra, 76(sp) +; CHECK-NEXT: sw t0, 72(sp) +; CHECK-NEXT: sw t1, 68(sp) +; CHECK-NEXT: sw t2, 64(sp) +; CHECK-NEXT: sw s0, 60(sp) +; CHECK-NEXT: sw a0, 56(sp) +; CHECK-NEXT: sw a1, 52(sp) +; CHECK-NEXT: sw a2, 48(sp) +; CHECK-NEXT: sw a3, 44(sp) +; CHECK-NEXT: sw a4, 40(sp) +; CHECK-NEXT: sw a5, 36(sp) +; CHECK-NEXT: sw a6, 32(sp) +; CHECK-NEXT: sw a7, 28(sp) +; CHECK-NEXT: sw t3, 24(sp) +; CHECK-NEXT: sw t4, 20(sp) +; CHECK-NEXT: sw t5, 16(sp) +; CHECK-NEXT: sw t6, 12(sp) +; CHECK-NEXT: addi s0, sp, 80 +; CHECK-NEXT: call otherfoo +; CHECK-NEXT: lw t6, 12(sp) +; CHECK-NEXT: lw t5, 16(sp) +; CHECK-NEXT: lw t4, 20(sp) +; CHECK-NEXT: lw t3, 24(sp) +; CHECK-NEXT: lw a7, 28(sp) +; CHECK-NEXT: lw a6, 32(sp) +; CHECK-NEXT: lw a5, 36(sp) +; CHECK-NEXT: lw a4, 40(sp) +; CHECK-NEXT: lw a3, 44(sp) +; CHECK-NEXT: lw a2, 48(sp) +; CHECK-NEXT: lw a1, 52(sp) +; CHECK-NEXT: lw a0, 56(sp) +; CHECK-NEXT: lw s0, 60(sp) +; CHECK-NEXT: lw t2, 64(sp) +; CHECK-NEXT: lw t1, 68(sp) +; CHECK-NEXT: lw t0, 72(sp) +; CHECK-NEXT: lw ra, 76(sp) +; CHECK-NEXT: addi sp, sp, 80 +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: mret + call void bitcast (void (...)* @otherfoo to void ()*)() + ret void +} + +define void @foo_preemptible_with_call() #2 { +; CHECK-LABEL: foo_preemptible_with_call: +; CHECK: # %bb.0: +; CHECK-NEXT: addi sp, sp, -80 +; CHECK-NEXT: sw s0, 12(sp) +; CHECK-NEXT: sw s1, 8(sp) +; CHECK-NEXT: csrr s0, mcause +; CHECK-NEXT: csrr s1, mepc +; CHECK-NEXT: csrsi mstatus, 8 +; CHECK-NEXT: sw ra, 76(sp) +; CHECK-NEXT: sw t0, 72(sp) +; CHECK-NEXT: sw t1, 68(sp) +; CHECK-NEXT: sw t2, 64(sp) +; CHECK-NEXT: sw a0, 60(sp) +; CHECK-NEXT: sw a1, 56(sp) +; CHECK-NEXT: sw a2, 52(sp) +; CHECK-NEXT: sw a3, 48(sp) +; CHECK-NEXT: sw a4, 44(sp) +; CHECK-NEXT: sw a5, 40(sp) +; CHECK-NEXT: sw a6, 36(sp) +; CHECK-NEXT: sw a7, 32(sp) +; CHECK-NEXT: sw t3, 28(sp) +; CHECK-NEXT: sw t4, 24(sp) +; CHECK-NEXT: sw t5, 20(sp) +; CHECK-NEXT: sw t6, 16(sp) +; CHECK-NEXT: call otherfoo +; CHECK-NEXT: lw t6, 16(sp) +; CHECK-NEXT: lw t5, 20(sp) +; CHECK-NEXT: lw t4, 24(sp) +; CHECK-NEXT: lw t3, 28(sp) +; CHECK-NEXT: lw a7, 32(sp) +; CHECK-NEXT: lw a6, 36(sp) +; CHECK-NEXT: lw a5, 40(sp) +; CHECK-NEXT: lw a4, 44(sp) +; CHECK-NEXT: lw a3, 48(sp) +; CHECK-NEXT: lw a2, 52(sp) +; CHECK-NEXT: lw a1, 56(sp) +; CHECK-NEXT: lw a0, 60(sp) +; CHECK-NEXT: lw t2, 64(sp) +; CHECK-NEXT: lw t1, 68(sp) +; CHECK-NEXT: lw t0, 72(sp) +; CHECK-NEXT: lw ra, 76(sp) +; CHECK-NEXT: csrci mstatus, 8 +; CHECK-NEXT: csrw mepc, s1 +; CHECK-NEXT: csrw mcause, s0 +; CHECK-NEXT: lw s1, 8(sp) +; CHECK-NEXT: lw s0, 12(sp) +; CHECK-NEXT: addi sp, sp, 80 +; CHECK-NEXT: mret + call void bitcast (void (...)* @otherfoo to void ()*)() + ret void +} + +define void @foo_preemptible_stack_swap_with_call() #3 { +; CHECK-LABEL: foo_preemptible_stack_swap_with_call: +; CHECK: # %bb.0: +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: addi sp, sp, -80 +; CHECK-NEXT: sw s0, 12(sp) +; CHECK-NEXT: sw s1, 8(sp) +; CHECK-NEXT: csrr s0, mcause +; CHECK-NEXT: csrr s1, mepc +; CHECK-NEXT: csrsi mstatus, 8 +; CHECK-NEXT: sw ra, 76(sp) +; CHECK-NEXT: sw t0, 72(sp) +; CHECK-NEXT: sw t1, 68(sp) +; CHECK-NEXT: sw t2, 64(sp) +; CHECK-NEXT: sw a0, 60(sp) +; CHECK-NEXT: sw a1, 56(sp) +; CHECK-NEXT: sw a2, 52(sp) +; CHECK-NEXT: sw a3, 48(sp) +; CHECK-NEXT: sw a4, 44(sp) +; CHECK-NEXT: sw a5, 40(sp) +; CHECK-NEXT: sw a6, 36(sp) +; CHECK-NEXT: sw a7, 32(sp) +; CHECK-NEXT: sw t3, 28(sp) +; CHECK-NEXT: sw t4, 24(sp) +; CHECK-NEXT: sw t5, 20(sp) +; CHECK-NEXT: sw t6, 16(sp) +; CHECK-NEXT: call otherfoo +; CHECK-NEXT: lw t6, 16(sp) +; CHECK-NEXT: lw t5, 20(sp) +; CHECK-NEXT: lw t4, 24(sp) +; CHECK-NEXT: lw t3, 28(sp) +; CHECK-NEXT: lw a7, 32(sp) +; CHECK-NEXT: lw a6, 36(sp) +; CHECK-NEXT: lw a5, 40(sp) +; CHECK-NEXT: lw a4, 44(sp) +; CHECK-NEXT: lw a3, 48(sp) +; CHECK-NEXT: lw a2, 52(sp) +; CHECK-NEXT: lw a1, 56(sp) +; CHECK-NEXT: lw a0, 60(sp) +; CHECK-NEXT: lw t2, 64(sp) +; CHECK-NEXT: lw t1, 68(sp) +; CHECK-NEXT: lw t0, 72(sp) +; CHECK-NEXT: lw ra, 76(sp) +; CHECK-NEXT: csrci mstatus, 8 +; CHECK-NEXT: csrw mepc, s1 +; CHECK-NEXT: csrw mcause, s0 +; CHECK-NEXT: lw s1, 8(sp) +; CHECK-NEXT: lw s0, 12(sp) +; CHECK-NEXT: addi sp, sp, 80 +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: mret + call void bitcast (void (...)* @otherfoo to void ()*)() + ret void +} + +; Repeat the above checks and check +; when the handler also uses s0 and s1 (x8 and x9), +; these callee-saved registers are saved on the stack. + +define void @test_stack_swap_use_s0_s1() #0 { +; CHECK-LABEL: test_stack_swap_use_s0_s1: +; CHECK: # %bb.0: +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: addi sp, sp, -16 +; CHECK-NEXT: sw s0, 12(sp) +; CHECK-NEXT: sw s1, 8(sp) +; CHECK-NEXT: #APP +; CHECK-NEXT: #NO_APP +; CHECK-NEXT: lw s1, 8(sp) +; CHECK-NEXT: lw s0, 12(sp) +; CHECK-NEXT: addi sp, sp, 16 +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: mret + call void asm sideeffect "", "~{x8},~{x9}"() #4 + ret void +} + +define void @foo_preemptible_use_s0_s1() #2 { +; CHECK-LABEL: foo_preemptible_use_s0_s1: +; CHECK: # %bb.0: +; CHECK-NEXT: addi sp, sp, -16 +; CHECK-NEXT: sw s0, 4(sp) +; CHECK-NEXT: sw s1, 0(sp) +; CHECK-NEXT: csrr s0, mcause +; CHECK-NEXT: csrr s1, mepc +; CHECK-NEXT: csrsi mstatus, 8 +; CHECK-NEXT: sw s0, 12(sp) +; CHECK-NEXT: sw s1, 8(sp) +; CHECK-NEXT: #APP +; CHECK-NEXT: #NO_APP +; CHECK-NEXT: lw s1, 8(sp) +; CHECK-NEXT: lw s0, 12(sp) +; CHECK-NEXT: csrci mstatus, 8 +; CHECK-NEXT: csrw mepc, s1 +; CHECK-NEXT: csrw mcause, s0 +; CHECK-NEXT: lw s1, 0(sp) +; CHECK-NEXT: lw s0, 4(sp) +; CHECK-NEXT: addi sp, sp, 16 +; CHECK-NEXT: mret + call void asm sideeffect "", "~{x8},~{x9}"() #4 + ret void +} + +define void @foo_preemptible_stack_swap_use_s0_s1() #3 { +; CHECK-LABEL: foo_preemptible_stack_swap_use_s0_s1: +; CHECK: # %bb.0: +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: addi sp, sp, -16 +; CHECK-NEXT: sw s0, 4(sp) +; CHECK-NEXT: sw s1, 0(sp) +; CHECK-NEXT: csrr s0, mcause +; CHECK-NEXT: csrr s1, mepc +; CHECK-NEXT: csrsi mstatus, 8 +; CHECK-NEXT: sw s0, 12(sp) +; CHECK-NEXT: sw s1, 8(sp) +; CHECK-NEXT: #APP +; CHECK-NEXT: #NO_APP +; CHECK-NEXT: lw s1, 8(sp) +; CHECK-NEXT: lw s0, 12(sp) +; CHECK-NEXT: csrci mstatus, 8 +; CHECK-NEXT: csrw mepc, s1 +; CHECK-NEXT: csrw mcause, s0 +; CHECK-NEXT: lw s1, 0(sp) +; CHECK-NEXT: lw s0, 4(sp) +; CHECK-NEXT: addi sp, sp, 16 +; CHECK-NEXT: csrrw sp, mscratchcsw, sp +; CHECK-NEXT: mret + call void asm sideeffect "", "~{x8},~{x9}"() #4 + ret void +} + +attributes #0 = { nounwind "interrupt"="SiFive-CLIC-stack-swap"} +attributes #1 = { nounwind "interrupt"="SiFive-CLIC-stack-swap" "frame-pointer"="all"} +attributes #2 = { nounwind "interrupt"="SiFive-CLIC-preemptible"} +attributes #3 = { nounwind "interrupt"="SiFive-CLIC-preemptible-stack-swap"} +attributes #4 = { nounwind } +