Skip to content

[RISCV] SiFive CLIC Support #132481

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Apr 26, 2025
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,12 @@ RISC-V Support
^^^^^^^^^^^^^^

- Add support for `-mtune=generic-ooo` (a generic out-of-order model).
- Adds support for `__attribute__((interrupt("SiFive-CLIC-preemptible")))` and
`__attribute__((interrupt("SiFive-CLIC-stack-swap")))`. The former
automatically saves some interrupt CSRs before re-enabling interrupts in the
function prolog, the latter swaps `sp` with the value in a CSR before it is
used or modified. These two can also be combined, and can be combined with
`interrupt("machine")`.

- Adds support for `__attribute__((interrupt("qci-nest")))` and
`__attribute__((interrupt("qci-nonest")))`. These use instructions from
Expand Down
21 changes: 17 additions & 4 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -2236,10 +2236,23 @@ def NoMicroMips : InheritableAttr, TargetSpecificAttr<TargetMips32> {
def RISCVInterrupt : InheritableAttr, TargetSpecificAttr<TargetRISCV> {
let Spellings = [GCC<"interrupt">];
let Subjects = SubjectList<[Function]>;
let Args = [EnumArgument<"Interrupt", "InterruptType", /*is_string=*/true,
["supervisor", "machine", "qci-nest", "qci-nonest"],
["supervisor", "machine", "qcinest", "qcinonest"],
1>];
let Args = [VariadicEnumArgument<"Interrupt", "InterruptType", /*is_string=*/true,
[
"supervisor",
"machine",
"qci-nest",
"qci-nonest",
"SiFive-CLIC-preemptible",
"SiFive-CLIC-stack-swap",
],
[
"supervisor",
"machine",
"qcinest",
"qcinonest",
"SiFiveCLICPreemptible",
"SiFiveCLICStackSwap",
]>];
let ParseKind = "Interrupt";
let Documentation = [RISCVInterruptDocs];
}
Expand Down
15 changes: 13 additions & 2 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -2863,8 +2863,9 @@ targets. This attribute may be attached to a function definition and instructs
the backend to generate appropriate function entry/exit code so that it can be
used directly as an interrupt service routine.

Permissible values for this parameter are ``supervisor``, ``machine``,
``qci-nest`` and ``qci-nonest``. If there is no parameter, then it defaults to
Permissible values for this parameter are ``machine``, ``supervisor``,
``qci-nest``, ``qci-nonest``, ``SiFive-CLIC-preemptible``, and
``SiFive-CLIC-stack-swap``. If there is no parameter, then it defaults to
``machine``.

The ``qci-nest`` and ``qci-nonest`` values require Qualcomm's Xqciint extension
Expand All @@ -2875,6 +2876,15 @@ restore interrupt state to the stack -- the ``qci-nest`` value will use
begin the interrupt handler. Both of these will use ``qc.c.mileaveret`` to
restore the state and return to the previous context.

The ``SiFive-CLIC-preemptible`` and ``SiFive-CLIC-stack-swap`` values are used
for machine-mode interrupts. For ``SiFive-CLIC-preemptible`` interrupts, the
values of ``mcause`` and ``mepc`` are saved onto the stack, and interrupts are
re-enabled. For ``SiFive-CLIC-stack-swap`` interrupts, the stack pointer is
swapped with ``mscratch`` before its first use and after its last use.

The SiFive CLIC values may be combined with each other and with the ``machine``
attribute value. Any other combination of different values is not allowed.

Repeated interrupt attribute on the same declaration will cause a warning
to be emitted. In case of repeated declarations, the last one prevails.

Expand All @@ -2884,6 +2894,7 @@ https://riscv.org/specifications/privileged-isa/
The RISC-V Instruction Set Manual Volume II: Privileged Architecture
Version 1.10.
https://github.com/quic/riscv-unified-db/releases/tag/Xqci-0.7
https://sifive.cdn.prismic.io/sifive/d1984d2b-c9b9-4c91-8de0-d68a5e64fa0f_sifive-interrupt-cookbook-v1p2.pdf
}];
}

Expand Down
4 changes: 3 additions & 1 deletion clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -12624,7 +12624,9 @@ def err_riscv_builtin_invalid_lmul : Error<
def err_riscv_type_requires_extension : Error<
"RISC-V type %0 requires the '%1' extension">;
def err_riscv_attribute_interrupt_requires_extension : Error<
"RISC-V interrupt attribute '%0' requires extension '%1'">;
"RISC-V 'interrupt' attribute '%0' requires extension '%1'">;
def err_riscv_attribute_interrupt_invalid_combination : Error<
"RISC-V 'interrupt' attribute contains invalid combination of interrupt types">;

def err_std_source_location_impl_not_found : Error<
"'std::source_location::__impl' was not found; it must be defined before '__builtin_source_location' is called">;
Expand Down
43 changes: 33 additions & 10 deletions clang/lib/CodeGen/Targets/RISCV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -829,16 +829,39 @@ class RISCVTargetCodeGenInfo : public TargetCodeGenInfo {
if (!Attr)
return;

const char *Kind;
switch (Attr->getInterrupt()) {
case RISCVInterruptAttr::supervisor: Kind = "supervisor"; break;
case RISCVInterruptAttr::machine: Kind = "machine"; break;
case RISCVInterruptAttr::qcinest:
Kind = "qci-nest";
break;
case RISCVInterruptAttr::qcinonest:
Kind = "qci-nonest";
break;
StringRef Kind = "machine";
bool HasSiFiveCLICPreemptible = false;
bool HasSiFiveCLICStackSwap = false;
for (RISCVInterruptAttr::InterruptType type : Attr->interrupt()) {
switch (type) {
case RISCVInterruptAttr::machine:
// Do not update `Kind` because `Kind` is already "machine", or the
// kinds also contains SiFive types which need to be applied.
break;
case RISCVInterruptAttr::supervisor:
Kind = "supervisor";
break;
case RISCVInterruptAttr::qcinest:
Kind = "qci-nest";
break;
case RISCVInterruptAttr::qcinonest:
Kind = "qci-nonest";
break;
// There are three different LLVM IR attribute values for SiFive CLIC
// interrupt kinds, one for each kind and one extra for their combination.
case RISCVInterruptAttr::SiFiveCLICPreemptible: {
HasSiFiveCLICPreemptible = true;
Kind = HasSiFiveCLICStackSwap ? "SiFive-CLIC-preemptible-stack-swap"
: "SiFive-CLIC-preemptible";
break;
}
case RISCVInterruptAttr::SiFiveCLICStackSwap: {
HasSiFiveCLICStackSwap = true;
Kind = HasSiFiveCLICPreemptible ? "SiFive-CLIC-preemptible-stack-swap"
: "SiFive-CLIC-stack-swap";
break;
}
}
}

Fn->addFnAttr("interrupt", Kind);
Expand Down
132 changes: 96 additions & 36 deletions clang/lib/Sema/SemaRISCV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "clang/Sema/SemaRISCV.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Attrs.inc"
#include "clang/AST/Decl.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/TargetBuiltins.h"
Expand Down Expand Up @@ -1454,25 +1455,14 @@ void SemaRISCV::handleInterruptAttr(Decl *D, const ParsedAttr &AL) {
return;
}

// Check the attribute argument. Argument is optional.
if (!AL.checkAtMostNumArgs(SemaRef, 1))
return;

StringRef Str;
SourceLocation ArgLoc;

// 'machine'is the default interrupt mode.
if (AL.getNumArgs() == 0)
Str = "machine";
else if (!SemaRef.checkStringLiteralArgumentAttr(AL, 0, Str, &ArgLoc))
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].
// - The attribute itself must have at most 2 arguments
// - The attribute arguments must be string literals, and valid choices.
// - The attribute arguments must be a valid combination
// - The current target must support the right extensions for the combination.

if (D->getFunctionType() == nullptr) {
Diag(D->getLocation(), diag::warn_attribute_wrong_decl_type)
Expand All @@ -1492,35 +1482,105 @@ void SemaRISCV::handleInterruptAttr(Decl *D, const ParsedAttr &AL) {
return;
}

RISCVInterruptAttr::InterruptType Kind;
if (!RISCVInterruptAttr::ConvertStrToInterruptType(Str, Kind)) {
Diag(AL.getLoc(), diag::warn_attribute_type_not_supported)
<< AL << Str << ArgLoc;
if (!AL.checkAtMostNumArgs(SemaRef, 2))
return;
}

switch (Kind) {
default:
break;
case RISCVInterruptAttr::InterruptType::qcinest:
case RISCVInterruptAttr::InterruptType::qcinonest: {
const TargetInfo &TI = getASTContext().getTargetInfo();
llvm::StringMap<bool> FunctionFeatureMap;
getASTContext().getFunctionFeatureMap(FunctionFeatureMap,
dyn_cast<FunctionDecl>(D));
bool HasSiFiveCLICType = false;
bool HasUnaryType = false;

SmallSet<RISCVInterruptAttr::InterruptType, 2> Types;
for (unsigned ArgIndex = 0; ArgIndex < AL.getNumArgs(); ++ArgIndex) {
RISCVInterruptAttr::InterruptType Type;
StringRef TypeString;
SourceLocation Loc;

if (!TI.hasFeature("experimental-xqciint") &&
!FunctionFeatureMap.lookup("experimental-xqciint")) {
Diag(AL.getLoc(), diag::err_riscv_attribute_interrupt_requires_extension)
<< Str << "Xqciint";
if (!SemaRef.checkStringLiteralArgumentAttr(AL, ArgIndex, TypeString, &Loc))
return;

if (!RISCVInterruptAttr::ConvertStrToInterruptType(TypeString, Type)) {
std::string TypeLiteral = ("\"" + TypeString + "\"").str();
Diag(AL.getLoc(), diag::warn_attribute_type_not_supported)
<< AL << TypeLiteral << Loc;
return;
}
break;

switch (Type) {
case RISCVInterruptAttr::machine:
// "machine" could be combined with the SiFive CLIC types, or could be
// just "machine".
break;
case RISCVInterruptAttr::SiFiveCLICPreemptible:
case RISCVInterruptAttr::SiFiveCLICStackSwap:
// SiFive-CLIC types can be combined with each other and "machine"
HasSiFiveCLICType = true;
break;
case RISCVInterruptAttr::supervisor:
case RISCVInterruptAttr::qcinest:
case RISCVInterruptAttr::qcinonest:
// "supervisor" and "qci-(no)nest" cannot be combined with any other types
HasUnaryType = true;
break;
}

Types.insert(Type);
}

if (HasUnaryType && Types.size() > 1) {
Diag(AL.getLoc(), diag::err_riscv_attribute_interrupt_invalid_combination);
return;
}

if (HasUnaryType && HasSiFiveCLICType) {
Diag(AL.getLoc(), diag::err_riscv_attribute_interrupt_invalid_combination);
return;
}

// "machine" is the default, if nothing is specified.
if (AL.getNumArgs() == 0)
Types.insert(RISCVInterruptAttr::machine);

const TargetInfo &TI = getASTContext().getTargetInfo();
llvm::StringMap<bool> FunctionFeatureMap;
getASTContext().getFunctionFeatureMap(FunctionFeatureMap,
dyn_cast<FunctionDecl>(D));

auto HasFeature = [&](StringRef FeatureName) -> bool {
return TI.hasFeature(FeatureName) || FunctionFeatureMap.lookup(FeatureName);
};

D->addAttr(::new (getASTContext())
RISCVInterruptAttr(getASTContext(), AL, Kind));
for (RISCVInterruptAttr::InterruptType Type : Types) {
switch (Type) {
// The QCI interrupt types require Xqciint
case RISCVInterruptAttr::qcinest:
case RISCVInterruptAttr::qcinonest: {
if (!HasFeature("experimental-xqciint")) {
Diag(AL.getLoc(),
diag::err_riscv_attribute_interrupt_requires_extension)
<< RISCVInterruptAttr::ConvertInterruptTypeToStr(Type) << "Xqciint";
return;
}
} break;
// The SiFive CLIC interrupt types require Xsfmclic
case RISCVInterruptAttr::SiFiveCLICPreemptible:
case RISCVInterruptAttr::SiFiveCLICStackSwap: {
if (!HasFeature("experimental-xsfmclic")) {
Diag(AL.getLoc(),
diag::err_riscv_attribute_interrupt_requires_extension)
<< RISCVInterruptAttr::ConvertInterruptTypeToStr(Type)
<< "XSfmclic";
return;
}
} break;
default:
break;
}
}

SmallVector<RISCVInterruptAttr::InterruptType, 2> TypesVec(Types.begin(),
Types.end());

D->addAttr(::new (getASTContext()) RISCVInterruptAttr(
getASTContext(), AL, TypesVec.data(), TypesVec.size()));
}

bool SemaRISCV::isAliasValid(unsigned BuiltinID, StringRef AliasName) {
Expand Down
2 changes: 2 additions & 0 deletions clang/test/Driver/print-supported-extensions-riscv.c
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@
// CHECK-NEXT: xqcisync 0.2 'Xqcisync' (Qualcomm uC Sync Delay Extension)
// CHECK-NEXT: xrivosvisni 0.1 'XRivosVisni' (Rivos Vector Integer Small New)
// CHECK-NEXT: xrivosvizip 0.1 'XRivosVizip' (Rivos Vector Register Zips)
// CHECK-NEXT: xsfmclic 0.1 'XSfmclic' (SiFive CLIC Machine-mode CSRs)
// CHECK-NEXT: xsfsclic 0.1 'XSfsclic' (SiFive CLIC Supervisor-mode CSRs)
// CHECK-EMPTY:
// CHECK-NEXT: Supported Profiles
// CHECK-NEXT: rva20s64
Expand Down
49 changes: 32 additions & 17 deletions clang/test/Sema/riscv-interrupt-attr-qci.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_cc1 -triple riscv32-unknown-elf -target-feature +experimental-xqciint -emit-llvm -DCHECK_IR < %s| FileCheck %s
// RUN: %clang_cc1 -triple riscv32-unknown-elf -target-feature +experimental-xqciint -emit-llvm -DCHECK_IR < %s | FileCheck %s
// RUN: %clang_cc1 %s -triple riscv32-unknown-elf -target-feature +experimental-xqciint -verify=enabled,both -fsyntax-only
// RUN: %clang_cc1 %s -triple riscv32-unknown-elf -verify=disabled,both -fsyntax-only
// RUN: %clang_cc1 %s -triple riscv32-unknown-elf -target-feature -experimental-xqciint -verify=disabled,both -fsyntax-only
Expand All @@ -11,29 +11,44 @@
__attribute__((interrupt("qci-nest")))
void foo_nest_interrupt(void) {}

// CHECK-LABEL: @foo_nonnest_interrupt() #1
// CHECK-LABEL: @foo_nest_nest_interrupt() #0
// CHECK: ret void
__attribute__((interrupt("qci-nest", "qci-nest")))
void foo_nest_nest_interrupt(void) {}

// CHECK-LABEL: @foo_nonest_interrupt() #1
// CHECK: ret void
__attribute__((interrupt("qci-nonest")))
void foo_nonnest_interrupt(void) {}
void foo_nonest_interrupt(void) {}

// CHECK-LABEL: @foo_nonest_nonest_interrupt() #1
// CHECK: ret void
__attribute__((interrupt("qci-nonest", "qci-nonest")))
void foo_nonest_nonest_interrupt(void) {}

// CHECK: attributes #0
// CHECK: "interrupt"="qci-nest"
// CHECK: attributes #1
// CHECK: "interrupt"="qci-nonest"
#else
// Test for QCI extension's interrupt attribute support
__attribute__((interrupt("qci-est"))) void foo_nest1(void) {} // both-warning {{'interrupt' attribute argument not supported: qci-est}}
__attribute__((interrupt("qci-noest"))) void foo_nonest1(void) {} // both-warning {{'interrupt' attribute argument not supported: qci-noest}}
__attribute__((interrupt(1))) void foo_nest2(void) {} // both-error {{expected string literal as argument of 'interrupt' attribute}}
__attribute__((interrupt("qci-nest", "qci-nonest"))) void foo1(void) {} // both-error {{'interrupt' attribute takes no more than 1 argument}}
__attribute__((interrupt("qci-nonest", "qci-nest"))) void foo2(void) {} // both-error {{'interrupt' attribute takes no more than 1 argument}}
__attribute__((interrupt("", "qci-nonest"))) void foo3(void) {} // both-error {{'interrupt' attribute takes no more than 1 argument}}
__attribute__((interrupt("", "qci-nest"))) void foo4(void) {} // both-error {{'interrupt' attribute takes no more than 1 argument}}
__attribute__((interrupt("qci-nonest", 1))) void foo5(void) {} // both-error {{'interrupt' attribute takes no more than 1 argument}}
__attribute__((interrupt("qci-nest", 1))) void foo6(void) {} // both-error {{'interrupt' attribute takes no more than 1 argument}}

__attribute__((interrupt("qci-nest"))) void foo_nest(void) {} // disabled-error {{RISC-V interrupt attribute 'qci-nest' requires extension 'Xqciint'}}
__attribute__((interrupt("qci-nonest"))) void foo_nonest(void) {} // disabled-error {{RISC-V interrupt attribute 'qci-nonest' requires extension 'Xqciint'}}
__attribute__((interrupt(1))) void foo1(void) {} // both-error {{expected string literal as argument of 'interrupt' attribute}}
__attribute__((interrupt("qci-nonest", 1))) void foo_nonest2(void) {} // both-error {{expected string literal as argument of 'interrupt' attribute}}
__attribute__((interrupt("qci-nest", 1))) void foo_nest2(void) {} // both-error {{expected string literal as argument of 'interrupt' attribute}}
__attribute__((interrupt("qci-est"))) void foo_nest3(void) {} // both-warning {{'interrupt' attribute argument not supported: "qci-est"}}
__attribute__((interrupt("qci-noest"))) void foo_nonest3(void) {} // both-warning {{'interrupt' attribute argument not supported: "qci-noest"}}
__attribute__((interrupt("", "qci-nonest"))) void foo_nonest4(void) {} // both-warning {{'interrupt' attribute argument not supported: ""}}
__attribute__((interrupt("", "qci-nest"))) void foo_nest4(void) {} // both-warning {{'interrupt' attribute argument not supported: ""}}

__attribute__((interrupt("qci-nonest", "qci-nest"))) void foo_nonest5(void) {} // both-error {{RISC-V 'interrupt' attribute contains invalid combination of interrupt types}}
__attribute__((interrupt("qci-nest", "qci-nonest"))) void foo_nest5(void) {} // both-error {{RISC-V 'interrupt' attribute contains invalid combination of interrupt types}}

__attribute__((interrupt("qci-nest"))) void foo_nest(void) {} // disabled-error {{RISC-V 'interrupt' attribute 'qci-nest' requires extension 'Xqciint'}}
__attribute__((interrupt("qci-nonest"))) void foo_nonest(void) {} // disabled-error {{RISC-V 'interrupt' attribute 'qci-nonest' requires extension 'Xqciint'}}

__attribute__((interrupt("qci-nest", "qci-nest"))) void foo_nest_nest(void) {} // disabled-error {{RISC-V 'interrupt' attribute 'qci-nest' requires extension 'Xqciint'}}
__attribute__((interrupt("qci-nonest", "qci-nonest"))) void foo_nonest_nonest(void) {} // disabled-error {{RISC-V 'interrupt' attribute 'qci-nonest' requires extension 'Xqciint'}}


// This tests the errors for the qci interrupts when using
// `__attribute__((target(...)))` - but they fail on RV64, because you cannot
Expand All @@ -44,8 +59,8 @@ __attribute__((target("arch=+xqciint"))) __attribute__((interrupt("qci-nonest"))

// The attribute order is important, the interrupt attribute must come after the
// target attribute
__attribute__((interrupt("qci-nest"))) __attribute__((target("arch=+xqciint"))) void foo_nest_xqciint2(void) {} // disabled-error {{RISC-V interrupt attribute 'qci-nest' requires extension 'Xqciint'}}
__attribute__((interrupt("qci-nonest"))) __attribute__((target("arch=+xqciint"))) void foo_nonest_xqciint2(void) {} // disabled-error {{RISC-V interrupt attribute 'qci-nonest' requires extension 'Xqciint'}}
__attribute__((interrupt("qci-nest"))) __attribute__((target("arch=+xqciint"))) void foo_nest_xqciint2(void) {} // disabled-error {{RISC-V 'interrupt' attribute 'qci-nest' requires extension 'Xqciint'}}
__attribute__((interrupt("qci-nonest"))) __attribute__((target("arch=+xqciint"))) void foo_nonest_xqciint2(void) {} // disabled-error {{RISC-V 'interrupt' attribute 'qci-nonest' requires extension 'Xqciint'}}
#endif

#endif
Loading