[clang] [RISCV][clang] Support XSfmm ABI attributes (PR #206260)
Brandon Wu via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 8 19:25:53 PDT 2026
https://github.com/4vtomat updated https://github.com/llvm/llvm-project/pull/206260
>From 1867ae4b9f3d2e901829a7061f2dbfa51526c2c6 Mon Sep 17 00:00:00 2001
From: Brandon Wu <brandon.wu at sifive.com>
Date: Mon, 4 May 2026 15:27:57 +0900
Subject: [PATCH 1/2] [RISCV][clang] Support XSfmm ABI attributes
This patch introduces a new attribute keyword to describe the
architecture state for RISC-V, currently this is used by XSfmm matrix
state, detail usage is described in SiFive documentation: https://www.sifive.com/document-file/xsfmm-matrix-extensions-specification
We add five new function type attributes:
1. __riscv_in("xsfmm"): function reads from matrix state
2. __riscv_out("xsfmm"): function writes to matrix state
3. __riscv_inout("xsfmm"): function reads and writes matrix state
4. __riscv_preserves("xsfmm"): function doesn't read or write state
5. __riscv_new("xsfmm"): function initiates new matrix state
TODO: Support __xsfmm_preserves statement attribute. We should allow
non user-defined functions that are xsfmm unaware and is proved to not reading
or writing the state, e.g. libc, libm, etc. One of the approach is
providing statement attribute, e.g. __xsfmm_preserves
printf("hello\n");, to make sema checking recognize and be aware of this
call.
---
clang/include/clang/AST/TypeBase.h | 102 ++++++++++++-
clang/include/clang/Basic/Attr.td | 35 +++++
clang/include/clang/Basic/AttrDocs.td | 67 +++++++++
.../clang/Basic/DiagnosticSemaKinds.td | 9 ++
clang/lib/AST/ASTContext.cpp | 6 +-
clang/lib/AST/Type.cpp | 18 +++
clang/lib/AST/TypePrinter.cpp | 5 +
clang/lib/Sema/SemaChecking.cpp | 100 ++++++++++++
clang/lib/Sema/SemaType.cpp | 102 +++++++++++++
clang/test/Sema/sifive-xsfmm-func-attr.c | 142 ++++++++++++++++++
10 files changed, 577 insertions(+), 9 deletions(-)
create mode 100644 clang/test/Sema/sifive-xsfmm-func-attr.c
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index c9658775f0470..9654bdf34a196 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -4816,14 +4816,17 @@ class FunctionType : public Type {
LLVM_PREFERRED_TYPE(bool)
unsigned HasArmTypeAttributes : 1;
+ LLVM_PREFERRED_TYPE(bool)
+ unsigned HasRISCVTypeAttributes : 1;
+
LLVM_PREFERRED_TYPE(bool)
unsigned EffectsHaveConditions : 1;
unsigned NumFunctionEffects : 4;
FunctionTypeExtraBitfields()
: NumExceptionType(0), HasExtraAttributeInfo(false),
- HasArmTypeAttributes(false), EffectsHaveConditions(false),
- NumFunctionEffects(0) {}
+ HasArmTypeAttributes(false), HasRISCVTypeAttributes(false),
+ EffectsHaveConditions(false), NumFunctionEffects(0) {}
};
/// A holder for extra information from attributes which aren't part of an
@@ -4869,6 +4872,46 @@ class FunctionType : public Type {
ARM_InOut = 4,
};
+ /// RISC-V type attributes for states.
+ enum RISCVTypeAttributes : uint8_t {
+ RISCVNormalFunction = 0,
+
+ // Describes the value of the xsfmm tile state using RISCVStateValue.
+ // Each tile gets 3 bits to store its state.
+ RISCVXsfmmShift = 0,
+ RISCVXsfmmMask = 0b111 << RISCVXsfmmShift,
+
+ // Currently using 3 bits for xsfmm
+ RISCVAttributeMask = 0b111
+ };
+
+ enum RISCVStateValue : unsigned {
+ RISCVNone = 0,
+ RISCVIn = 1,
+ RISCVOut = 2,
+ RISCVInOut = 3,
+ RISCVPreserves = 4,
+ RISCVNew = 5,
+ };
+
+ static const char *ToRISCVStateString(RISCVStateValue St) {
+ switch (St) {
+ case RISCVNone:
+ return "none";
+ case RISCVIn:
+ return "__riscv_in";
+ case RISCVOut:
+ return "__riscv_out";
+ case RISCVInOut:
+ return "__riscv_inout";
+ case RISCVPreserves:
+ return "__riscv_preserves";
+ case RISCVNew:
+ return "__riscv_new";
+ }
+ llvm_unreachable("Invalid RISCV state value");
+ }
+
static ArmStateValue getArmZAState(unsigned AttrBits) {
return static_cast<ArmStateValue>((AttrBits & SME_ZAMask) >> SME_ZAShift);
}
@@ -4877,6 +4920,11 @@ class FunctionType : public Type {
return static_cast<ArmStateValue>((AttrBits & SME_ZT0Mask) >> SME_ZT0Shift);
}
+ static RISCVStateValue getRISCVXsfmmState(unsigned AttrBits) {
+ return static_cast<RISCVStateValue>((AttrBits & RISCVXsfmmMask) >>
+ RISCVXsfmmShift);
+ }
+
/// A holder for Arm type attributes as described in the Arm C/C++
/// Language extensions which are not particularly common to all
/// types and therefore accounted separately from FunctionTypeBitfields.
@@ -4889,6 +4937,13 @@ class FunctionType : public Type {
FunctionTypeArmAttributes() : AArch64SMEAttributes(SME_NormalFunction) {}
};
+ struct alignas(void *) FunctionTypeRISCVAttributes {
+ LLVM_PREFERRED_TYPE(RISCVTypeAttributes)
+ unsigned RISCVAttributes : 3;
+
+ FunctionTypeRISCVAttributes() : RISCVAttributes(RISCVNormalFunction) {}
+ };
+
protected:
FunctionType(TypeClass tc, QualType res, QualType Canonical,
TypeDependence Dependence, ExtInfo Info)
@@ -5366,9 +5421,11 @@ class FunctionProtoType final
FunctionProtoType, QualType, SourceLocation,
FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeExtraAttributeInfo,
- FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
- Expr *, FunctionDecl *, FunctionType::ExtParameterInfo, Qualifiers,
- FunctionEffect, EffectConditionExpr> {
+ FunctionType::FunctionTypeArmAttributes,
+ FunctionType::FunctionTypeRISCVAttributes,
+ FunctionType::ExceptionType, Expr *, FunctionDecl *,
+ FunctionType::ExtParameterInfo, Qualifiers, FunctionEffect,
+ EffectConditionExpr> {
friend class ASTContext; // ASTContext creates these.
friend TrailingObjects;
@@ -5471,14 +5528,18 @@ class FunctionProtoType final
unsigned CFIUncheckedCallee : 1;
LLVM_PREFERRED_TYPE(AArch64SMETypeAttributes)
unsigned AArch64SMEAttributes : 9;
+ LLVM_PREFERRED_TYPE(RISCVTypeAttributes)
+ unsigned RISCVAttributes : 3;
ExtProtoInfo()
: Variadic(false), HasTrailingReturn(false), CFIUncheckedCallee(false),
- AArch64SMEAttributes(SME_NormalFunction) {}
+ AArch64SMEAttributes(SME_NormalFunction),
+ RISCVAttributes(RISCVNormalFunction) {}
ExtProtoInfo(CallingConv CC)
: ExtInfo(CC), Variadic(false), HasTrailingReturn(false),
- CFIUncheckedCallee(false), AArch64SMEAttributes(SME_NormalFunction) {}
+ CFIUncheckedCallee(false), AArch64SMEAttributes(SME_NormalFunction),
+ RISCVAttributes(RISCVNormalFunction) {}
ExtProtoInfo withExceptionSpec(const ExceptionSpecInfo &ESI) {
ExtProtoInfo Result(*this);
@@ -5495,6 +5556,7 @@ class FunctionProtoType final
bool requiresFunctionProtoTypeExtraBitfields() const {
return ExceptionSpec.Type == EST_Dynamic ||
requiresFunctionProtoTypeArmAttributes() ||
+ requiresFunctionProtoTypeRISCVAttributes() ||
requiresFunctionProtoTypeExtraAttributeInfo() ||
!FunctionEffects.empty();
}
@@ -5513,6 +5575,14 @@ class FunctionProtoType final
else
AArch64SMEAttributes &= ~Kind;
}
+
+ bool requiresFunctionProtoTypeRISCVAttributes() const {
+ return RISCVAttributes != RISCVNormalFunction;
+ }
+
+ void setRISCVAttribute(RISCVTypeAttributes Kind) {
+ RISCVAttributes |= Kind;
+ }
};
private:
@@ -5528,6 +5598,11 @@ class FunctionProtoType final
return hasArmTypeAttributes();
}
+ unsigned
+ numTrailingObjects(OverloadToken<FunctionTypeRISCVAttributes>) const {
+ return hasRISCVTypeAttributes();
+ }
+
unsigned numTrailingObjects(OverloadToken<FunctionTypeExtraBitfields>) const {
return hasExtraBitfields();
}
@@ -5641,6 +5716,12 @@ class FunctionProtoType final
->HasArmTypeAttributes;
}
+ bool hasRISCVTypeAttributes() const {
+ return FunctionTypeBits.HasExtraBitfields &&
+ getTrailingObjects<FunctionTypeExtraBitfields>()
+ ->HasRISCVTypeAttributes;
+ }
+
bool hasExtQualifiers() const {
return FunctionTypeBits.HasExtQuals;
}
@@ -5670,6 +5751,7 @@ class FunctionProtoType final
EPI.ExtParameterInfos = getExtParameterInfosOrNull();
EPI.ExtraAttributeInfo = getExtraAttributeInfo();
EPI.AArch64SMEAttributes = getAArch64SMEAttributes();
+ EPI.RISCVAttributes = getRISCVAttributes();
EPI.FunctionEffects = getFunctionEffects();
return EPI;
}
@@ -5872,6 +5954,12 @@ class FunctionProtoType final
->AArch64SMEAttributes;
}
+ unsigned getRISCVAttributes() const {
+ if (!hasRISCVTypeAttributes())
+ return RISCVNormalFunction;
+ return getTrailingObjects<FunctionTypeRISCVAttributes>()->RISCVAttributes;
+ }
+
ExtParameterInfo getExtParameterInfo(unsigned I) const {
assert(I < getNumParams() && "parameter index out of range");
if (hasExtParameterInfos())
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index a222092cd42cf..9e78094640b33 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3583,6 +3583,41 @@ def RISCVVLSCC: DeclOrTypeAttr, TargetSpecificAttr<TargetRISCV> {
let Documentation = [RISCVVLSCCDocs];
}
+def RISCVIn : TypeAttr, TargetSpecificAttr<TargetRISCV> {
+ let Spellings = [RegularKeyword<"__riscv_in">];
+ let Args = [VariadicStringArgument<"InArgs">];
+ let Subjects = SubjectList<[HasFunctionProto], ErrorDiag>;
+ let Documentation = [RISCVInDocs];
+}
+
+def RISCVOut : TypeAttr, TargetSpecificAttr<TargetRISCV> {
+ let Spellings = [RegularKeyword<"__riscv_out">];
+ let Args = [VariadicStringArgument<"OutArgs">];
+ let Subjects = SubjectList<[HasFunctionProto], ErrorDiag>;
+ let Documentation = [RISCVOutDocs];
+}
+
+def RISCVInOut : TypeAttr, TargetSpecificAttr<TargetRISCV> {
+ let Spellings = [RegularKeyword<"__riscv_inout">];
+ let Args = [VariadicStringArgument<"InOutArgs">];
+ let Subjects = SubjectList<[HasFunctionProto], ErrorDiag>;
+ let Documentation = [RISCVInOutDocs];
+}
+
+def RISCVPreserves : TypeAttr, TargetSpecificAttr<TargetRISCV> {
+ let Spellings = [RegularKeyword<"__riscv_preserves">];
+ let Args = [VariadicStringArgument<"PreserveArgs">];
+ let Subjects = SubjectList<[HasFunctionProto], ErrorDiag>;
+ let Documentation = [RISCVPreservesDocs];
+}
+
+def RISCVNew : TypeAttr, TargetSpecificAttr<TargetRISCV> {
+ let Spellings = [RegularKeyword<"__riscv_new">];
+ let Args = [VariadicStringArgument<"NewArgs">];
+ let Subjects = SubjectList<[HasFunctionProto], ErrorDiag>;
+ let Documentation = [RISCVNewDocs];
+}
+
def Target : InheritableAttr {
let Spellings = [GCC<"target">];
let Args = [StringArgument<"featuresStr">];
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 04362de2d5be2..c86891c3c5cea 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -6787,6 +6787,73 @@ the ABI. This variant aims to pass fixed-length vectors via vector registers,
if possible, rather than through general-purpose registers.}];
}
+def RISCVInDocs : Documentation {
+ let Category = DocCatFunction;
+ let Heading = "__riscv_in";
+ let Content = [{
+The ``__riscv_in(S)`` attribute indicates that a function reads from state S.
+This attribute is usually used in RISC-V matrix extensions to specify that the
+function will read from a matrix state passed as arguments.
+This helps compiler checks to prevent common programming errors that could lead
+to undefined behavior, data corruption, or incorrect computation results when
+working with matrix operations.
+ }];
+}
+
+def RISCVOutDocs : Documentation {
+ let Category = DocCatFunction;
+ let Heading = "__riscv_out";
+ let Content = [{
+The ``__riscv_out(S)`` attribute indicates that a function writes to state S.
+This attribute is usually used in RISC-V matrix extensions to specify that the
+function will write to a matrix state passed as arguments.
+This helps compiler checks to prevent common programming errors that could lead
+to undefined behavior, data corruption, or incorrect computation results when
+working with matrix operations.
+ }];
+}
+
+def RISCVInOutDocs : Documentation {
+ let Category = DocCatFunction;
+ let Heading = "__riscv_inout";
+ let Content = [{
+The ``__riscv_inout(S)`` attribute indicates that a function reads from and
+writes to state S.
+This attribute is usually used in RISC-V matrix extensions to specify that the
+function will read from and write to a matrix state passed as arguments.
+This helps compiler checks to prevent common programming errors that could lead
+to undefined behavior, data corruption, or incorrect computation results when
+working with matrix operations.
+ }];
+}
+
+def RISCVPreservesDocs : Documentation {
+ let Category = DocCatFunction;
+ let Heading = "__riscv_preserves";
+ let Content = [{
+The ``__riscv_preserves(S)`` attribute indicates that a function neither reads
+from nor writes to state S.
+This attribute is usually used in RISC-V matrix extensions to specify that the
+function will not read from or write to a matrix state passed as arguments.
+This helps compiler checks to prevent common programming errors that could lead
+to undefined behavior, data corruption, or incorrect computation results when
+working with matrix operations.
+ }];
+}
+
+def RISCVNewDocs : Documentation {
+ let Category = DocCatFunction;
+ let Heading = "__riscv_new";
+ let Content = [{
+The ``__riscv_new(S)`` attribute indicates that a function initiates a new state.
+This attribute is usually used in RISC-V matrix extensions to specify that the
+function will initiate a new matrix state passed as arguments.
+This helps compiler checks to prevent common programming errors that could lead
+to undefined behavior, data corruption, or incorrect computation results when
+working with matrix operations.
+ }];
+}
+
def PreferredNameDocs : Documentation {
let Category = DocCatDecl;
let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f7fba8df1e4d7..42a55dcdf0128 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13619,6 +13619,15 @@ def err_riscv_attribute_interrupt_requires_extension : Error<
def err_riscv_attribute_interrupt_invalid_combination : Error<
"RISC-V 'interrupt' attribute contains invalid combination of interrupt types">;
def err_riscv_builtin_invalid_twiden : Error<"RISC-V XSfmm twiden must be 1, 2 or 4">;
+// RISC-V errors
+def err_riscv_call_invalid_features : Error<
+ "call to an attributed function requires %0">;
+def err_missing_riscv_state : Error<"missing state for %0">;
+def err_unknown_riscv_state : Error<"unknown state '%0'">;
+def err_conflicting_attributes_riscv_state : Error<
+ "conflicting attribute. Description: %0">;
+def err_mutually_exclusive_attributes_riscv_state : Error<
+ "mutually exclusive attributes for state '%0'">;
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">;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index abf0cd5e18c2b..c02b43c947072 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -5161,12 +5161,14 @@ QualType ASTContext::getFunctionTypeInternal(
size_t Size = FunctionProtoType::totalSizeToAlloc<
QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeExtraAttributeInfo,
- FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
+ FunctionType::FunctionTypeArmAttributes,
+ FunctionType::FunctionTypeRISCVAttributes, FunctionType::ExceptionType,
Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo, Qualifiers,
FunctionEffect, EffectConditionExpr>(
NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
EPI.requiresFunctionProtoTypeExtraAttributeInfo(),
- EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType,
+ EPI.requiresFunctionProtoTypeArmAttributes(),
+ EPI.requiresFunctionProtoTypeRISCVAttributes(), ESH.NumExceptionType,
ESH.NumExprPtr, ESH.NumFunctionDeclPtr,
EPI.ExtParameterInfos ? NumArgs : 0,
EPI.TypeQuals.hasNonFastQualifiers() ? 1 : 0, EPI.FunctionEffects.size(),
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index b7bef40ca89f3..f704f748f7055 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3819,6 +3819,15 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
ExtraBits.HasArmTypeAttributes = true;
}
+ if (epi.requiresFunctionProtoTypeRISCVAttributes()) {
+ auto &RISCVTypeAttrs = *getTrailingObjects<FunctionTypeRISCVAttributes>();
+ RISCVTypeAttrs = FunctionTypeRISCVAttributes();
+
+ // Also set the bit in FunctionTypeExtraBitfields
+ auto &ExtraBits = *getTrailingObjects<FunctionTypeExtraBitfields>();
+ ExtraBits.HasRISCVTypeAttributes = true;
+ }
+
// Fill in the trailing argument array.
auto *argSlot = getTrailingObjects<QualType>();
for (unsigned i = 0; i != getNumParams(); ++i) {
@@ -3835,6 +3844,14 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
ArmTypeAttrs.AArch64SMEAttributes = epi.AArch64SMEAttributes;
}
+ // Propagate the RISC-V state attributes.
+ if (epi.RISCVAttributes != RISCVNormalFunction) {
+ auto &RISCVTypeAttrs = *getTrailingObjects<FunctionTypeRISCVAttributes>();
+ assert(epi.RISCVAttributes <= RISCVAttributeMask &&
+ "Not enough bits to encode RISC-V attributes");
+ RISCVTypeAttrs.RISCVAttributes = epi.RISCVAttributes;
+ }
+
// Fill in the exception type array if present.
if (getExceptionSpecType() == EST_Dynamic) {
auto &ExtraBits = *getTrailingObjects<FunctionTypeExtraBitfields>();
@@ -4071,6 +4088,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
ID.AddInteger((EffectCount << 3) | (HasConds << 2) |
(epi.AArch64SMEAttributes << 1) | epi.HasTrailingReturn);
ID.AddInteger(epi.CFIUncheckedCallee);
+ ID.AddInteger(epi.RISCVAttributes);
for (unsigned Idx = 0; Idx != EffectCount; ++Idx) {
ID.AddInteger(epi.FunctionEffects.Effects[Idx].toOpaqueInt32());
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index e8fbffb9f954d..04d990d028694 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -2060,6 +2060,11 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::ArmOut:
case attr::ArmInOut:
case attr::ArmPreserves:
+ case attr::RISCVIn:
+ case attr::RISCVOut:
+ case attr::RISCVInOut:
+ case attr::RISCVPreserves:
+ case attr::RISCVNew:
case attr::NonBlocking:
case attr::NonAllocating:
case attr::Blocking:
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index ec4a9037f5c23..13a15f4bb0b83 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -4536,6 +4536,106 @@ void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
Diag(Loc, diag::note_sme_use_preserves_za);
}
}
+
+ // Check if there's any conflicting call for every state, it should not be
+ // any conflict if caller and callee are in different state.
+
+ if (CallerFD &&
+ (!FD || !FD->getBuiltinID() ||
+ Context.BuiltinInfo.isLibFunction(FD->getBuiltinID()) ||
+ Context.BuiltinInfo.isPredefinedLibFunction(FD->getBuiltinID()))) {
+ QualType CallerType = CallerFD->getType();
+ if (!CallerType.isNull()) {
+ if (const auto *FPT = CallerType->getAs<FunctionProtoType>()) {
+ FunctionProtoType::ExtProtoInfo CallerExtInfo =
+ FPT->getExtProtoInfo();
+ llvm::StringMap<bool> CallerFeatureMap;
+ if (CallerExtInfo.RISCVAttributes & FunctionType::RISCVAttributeMask)
+ Context.getFunctionFeatureMap(CallerFeatureMap, CallerFD);
+ // tuple(CallerAttr, CalleeAttr, required feature)
+ const std::tuple<FunctionType::RISCVStateValue,
+ FunctionType::RISCVStateValue, StringRef>
+ RISCVStateInfo[] = {
+ {FunctionType::getRISCVXsfmmState(
+ CallerExtInfo.RISCVAttributes),
+ FunctionType::getRISCVXsfmmState(ExtInfo.RISCVAttributes),
+ "xsfmmbase"}};
+ for (auto [CallerAttr, CalleeAttr, RequiredFeature] :
+ RISCVStateInfo) {
+ // If both caller and callee are not attributed, then we're fine.
+ if (CallerAttr == FunctionType::RISCVNone &&
+ CalleeAttr == FunctionType::RISCVNone)
+ continue;
+
+ if (!Context.getTargetInfo().hasFeature(RequiredFeature) &&
+ !CallerFeatureMap.lookup(RequiredFeature)) {
+ // check if corresponding attributes are enabled.
+ Diag(Loc, diag::err_riscv_call_invalid_features)
+ << RequiredFeature;
+ continue;
+ }
+
+ switch (CallerAttr) {
+ case FunctionType::RISCVNone:
+ if (CalleeAttr != FunctionType::RISCVNew) {
+ // Check limitation:
+ // 1. Only __riscv_new function can be called in non-attributed
+ // function.
+ Diag(Loc, diag::err_conflicting_attributes_riscv_state)
+ << "Only __riscv_new function can be called in "
+ "non-attributed function.";
+ }
+ break;
+ case FunctionType::RISCVIn:
+ if (CalleeAttr != FunctionType::RISCVIn &&
+ CalleeAttr != FunctionType::RISCVPreserves) {
+ // 2. Function with __riscv_in can only call __riscv_in and
+ // __riscv_preserves function.
+ Diag(Loc, diag::err_conflicting_attributes_riscv_state)
+ << "Function with __riscv_in can only call __riscv_in and "
+ "__riscv_preserves function.";
+ }
+ break;
+ case FunctionType::RISCVOut:
+ if (CalleeAttr != FunctionType::RISCVIn &&
+ CalleeAttr != FunctionType::RISCVOut &&
+ CalleeAttr != FunctionType::RISCVPreserves) {
+ // 3. Function with __riscv_out can only call
+ // __riscv_in, __riscv_out and __riscv_preserves function.
+ Diag(Loc, diag::err_conflicting_attributes_riscv_state)
+ << "Function with __riscv_out can only call "
+ "__riscv_in, __riscv_out and __riscv_preserves "
+ "function.";
+ }
+ break;
+ case FunctionType::RISCVPreserves:
+ if (CalleeAttr != FunctionType::RISCVPreserves) {
+ // 4. Function with __riscv_preserves can only call
+ // __riscv_preserves function.
+ Diag(Loc, diag::err_conflicting_attributes_riscv_state)
+ << "Function with __riscv_preserves can only call "
+ "__riscv_preserves function.";
+ }
+ break;
+ case FunctionType::RISCVNew:
+ case FunctionType::RISCVInOut:
+ if (CalleeAttr == FunctionType::RISCVNone ||
+ CalleeAttr == FunctionType::RISCVNew) {
+ // Handle remainings: __riscv_new, __riscv_inout
+ // 5. Function with attribute can only call function with
+ // attribute(except for __riscv_new), i.e. __riscv_new and
+ // non-attributed function can not be called in attributed
+ // function.
+ Diag(Loc, diag::err_conflicting_attributes_riscv_state)
+ << "__riscv_new and non-attributed function can not be "
+ "called in attributed function.";
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
}
if (FDecl && FDecl->hasAttr<AllocAlignAttr>()) {
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index d2bb312feadc1..b31c900c1a362 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -166,6 +166,11 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
case ParsedAttr::AT_ArmAgnostic: \
case ParsedAttr::AT_AnyX86NoCallerSavedRegisters: \
case ParsedAttr::AT_AnyX86NoCfCheck: \
+ case ParsedAttr::AT_RISCVIn: \
+ case ParsedAttr::AT_RISCVOut: \
+ case ParsedAttr::AT_RISCVInOut: \
+ case ParsedAttr::AT_RISCVPreserves: \
+ case ParsedAttr::AT_RISCVNew: \
CALLING_CONV_ATTRS_CASELIST
// Microsoft-specific type qualifiers.
@@ -8041,6 +8046,51 @@ static bool handleArmStateAttribute(Sema &S,
return false;
}
+static bool handleRISCVStateAttribute(Sema &S,
+ FunctionProtoType::ExtProtoInfo &EPI,
+ ParsedAttr &Attr,
+ FunctionType::RISCVStateValue State) {
+ if (!Attr.getNumArgs()) {
+ S.Diag(Attr.getLoc(), diag::err_missing_riscv_state) << Attr;
+ Attr.setInvalid();
+ return true;
+ }
+
+ for (unsigned I = 0; I < Attr.getNumArgs(); ++I) {
+ StringRef StateName;
+ SourceLocation LiteralLoc;
+ if (!S.checkStringLiteralArgumentAttr(Attr, I, StateName, &LiteralLoc))
+ return true;
+
+ unsigned Shift;
+ FunctionType::RISCVStateValue ExistingState;
+
+ // Determine which tile state this is and get its shift/mask
+ if (StateName == "xsfmm") {
+ Shift = FunctionType::RISCVXsfmmShift;
+ ExistingState = FunctionType::getRISCVXsfmmState(EPI.RISCVAttributes);
+ } else {
+ S.Diag(LiteralLoc, diag::err_unknown_riscv_state) << StateName;
+ Attr.setInvalid();
+ return true;
+ }
+
+ // __riscv_in, __riscv_out, __riscv_inout, __riscv_preserves, and
+ // __riscv_new are all mutually exclusive for the same state,
+ // so check if there are conflicting attributes.
+ if (ExistingState != FunctionType::RISCVNone && ExistingState != State) {
+ S.Diag(LiteralLoc, diag::err_mutually_exclusive_attributes_riscv_state)
+ << StateName;
+ Attr.setInvalid();
+ return true;
+ }
+
+ EPI.setRISCVAttribute(
+ (FunctionType::RISCVTypeAttributes)((State << Shift)));
+ }
+ return false;
+}
+
/// Process an individual function attribute. Returns true to
/// indicate that the attribute was handled, false if it wasn't.
static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
@@ -8293,6 +8343,58 @@ static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
return true;
}
+ if (attr.getKind() == ParsedAttr::AT_RISCVIn ||
+ attr.getKind() == ParsedAttr::AT_RISCVOut ||
+ attr.getKind() == ParsedAttr::AT_RISCVInOut ||
+ attr.getKind() == ParsedAttr::AT_RISCVPreserves ||
+ attr.getKind() == ParsedAttr::AT_RISCVNew) {
+ if (S.CheckAttrTarget(attr))
+ return true;
+
+ if (!unwrapped.isFunctionType())
+ return false;
+
+ const auto *FnTy = unwrapped.get()->getAs<FunctionProtoType>();
+ if (!FnTy) {
+ S.Diag(attr.getLoc(), diag::warn_attribute_wrong_decl_type)
+ << attr << attr.isRegularKeywordAttribute()
+ << ExpectedFunctionWithProtoType;
+ attr.setInvalid();
+ return false;
+ }
+
+ FunctionProtoType::ExtProtoInfo EPI = FnTy->getExtProtoInfo();
+ switch (attr.getKind()) {
+ case ParsedAttr::AT_RISCVIn:
+ if (handleRISCVStateAttribute(S, EPI, attr, FunctionType::RISCVIn))
+ return true;
+ break;
+ case ParsedAttr::AT_RISCVOut:
+ if (handleRISCVStateAttribute(S, EPI, attr, FunctionType::RISCVOut))
+ return true;
+ break;
+ case ParsedAttr::AT_RISCVInOut:
+ if (handleRISCVStateAttribute(S, EPI, attr, FunctionType::RISCVInOut))
+ return true;
+ break;
+ case ParsedAttr::AT_RISCVPreserves:
+ if (handleRISCVStateAttribute(S, EPI, attr, FunctionType::RISCVPreserves))
+ return true;
+ break;
+ case ParsedAttr::AT_RISCVNew:
+ if (handleRISCVStateAttribute(S, EPI, attr, FunctionType::RISCVNew))
+ return true;
+ break;
+ default:
+ llvm_unreachable("Unsupported attribute");
+ }
+
+ QualType newtype = S.Context.getFunctionType(FnTy->getReturnType(),
+ FnTy->getParamTypes(), EPI);
+ type = unwrapped.wrap(S, newtype->getAs<FunctionType>());
+ return true;
+ }
+
if (attr.getKind() == ParsedAttr::AT_NoThrow) {
// Delay if this is not a function type.
if (!unwrapped.isFunctionType())
diff --git a/clang/test/Sema/sifive-xsfmm-func-attr.c b/clang/test/Sema/sifive-xsfmm-func-attr.c
new file mode 100644
index 0000000000000..c9f84b7847f47
--- /dev/null
+++ b/clang/test/Sema/sifive-xsfmm-func-attr.c
@@ -0,0 +1,142 @@
+// RUN: %clang_cc1 -triple riscv64-none-linux-gnu -target-feature +xsfmmbase -Wno-error=implicit-function-declaration -fsyntax-only -verify %s
+
+#include <riscv_vector.h>
+
+int xsfmm_in_callee(void) __riscv_in("xsfmm");
+int xsfmm_out_callee(void) __riscv_out("xsfmm");
+int xsfmm_inout_callee(void) __riscv_inout("xsfmm");
+int xsfmm_preserves_callee(void) __riscv_preserves("xsfmm");
+int xsfmm_new_callee(void) __riscv_new("xsfmm");
+
+void valid_new(void) {
+ xsfmm_new_callee();
+}
+
+void valid_preserves_in_in(void) __riscv_in("xsfmm") {
+ xsfmm_preserves_callee();
+}
+
+void valid_in_in_in(void) __riscv_in("xsfmm") {
+ xsfmm_in_callee();
+}
+
+void valid_in_in_out(void) __riscv_out("xsfmm") {
+ xsfmm_in_callee();
+}
+
+void valid_out_in_out(void) __riscv_out("xsfmm") {
+ xsfmm_out_callee();
+}
+
+void valid_preserves_in_out(void) __riscv_out("xsfmm") {
+ xsfmm_preserves_callee();
+}
+
+void valid_preserves_in_preserves(void) __riscv_preserves("xsfmm") {
+ xsfmm_preserves_callee();
+}
+
+vint32m1_t valid_in_intrinsic(vint32m1_t v, unsigned vl) __riscv_in("xsfmm") {
+ unsigned avl = __riscv_vsetvl_e32m1(vl);
+ return __riscv_vadd(v, v, avl);
+}
+
+vint32m1_t valid_out_intrinsic(vint32m1_t v, unsigned vl) __riscv_out("xsfmm") {
+ unsigned avl = __riscv_vsetvl_e32m1(vl);
+ return __riscv_vadd(v, v, avl);
+}
+
+vint32m1_t valid_inout_intrinsic(vint32m1_t v, unsigned vl) __riscv_inout("xsfmm") {
+ unsigned avl = __riscv_vsetvl_e32m1(vl);
+ return __riscv_vadd(v, v, avl);
+}
+
+vint32m1_t valid_preserves_intrinsic(vint32m1_t v, unsigned vl) __riscv_preserves("xsfmm") {
+ unsigned avl = __riscv_vsetvl_e32m1(vl);
+ return __riscv_vadd(v, v, avl);
+}
+
+vint32m1_t valid_new_intrinsic(vint32m1_t v, unsigned vl) __riscv_new("xsfmm") {
+ unsigned avl = __riscv_vsetvl_e32m1(vl);
+ return __riscv_vadd(v, v, avl);
+}
+
+void invalid_mutual_exclusive1(void) __riscv_in("xsfmm") __riscv_out("xsfmm") { // expected-error {{mutually exclusive attributes for state 'xsfmm'}}
+}
+
+void invalid_mutual_exclusive2(void) __riscv_in("xsfmm") __riscv_inout("xsfmm") { // expected-error {{mutually exclusive attributes for state 'xsfmm'}}
+}
+
+void invalid_mutual_exclusive3(void) __riscv_in("xsfmm") __riscv_preserves("xsfmm") { // expected-error {{mutually exclusive attributes for state 'xsfmm'}}
+}
+
+void invalid_mutual_exclusive4(void) __riscv_in("xsfmm") __riscv_new("xsfmm") { // expected-error {{mutually exclusive attributes for state 'xsfmm'}}
+}
+
+void invalid_mutual_exclusive5(void) __riscv_out("xsfmm") __riscv_inout("xsfmm") { // expected-error {{mutually exclusive attributes for state 'xsfmm'}}
+}
+
+void invalid_mutual_exclusive6(void) __riscv_out("xsfmm") __riscv_preserves("xsfmm") { // expected-error {{mutually exclusive attributes for state 'xsfmm'}}
+}
+
+void invalid_mutual_exclusive7(void) __riscv_out("xsfmm") __riscv_new("xsfmm") { // expected-error {{mutually exclusive attributes for state 'xsfmm'}}
+}
+
+void invalid_mutual_exclusive8(void) __riscv_inout("xsfmm") __riscv_preserves("xsfmm") { // expected-error {{mutually exclusive attributes for state 'xsfmm'}}
+}
+
+void invalid_mutual_exclusive9(void) __riscv_inout("xsfmm") __riscv_new("xsfmm") { // expected-error {{mutually exclusive attributes for state 'xsfmm'}}
+}
+
+void invalid_mutual_exclusive10(void) __riscv_preserves("xsfmm") __riscv_new("xsfmm") { // expected-error {{mutually exclusive attributes for state 'xsfmm'}}
+}
+
+void invalid_in_in_preserves(void) __riscv_preserves("xsfmm") {
+ xsfmm_in_callee(); // expected-error {{conflicting attribute. Description: Function with __riscv_preserves can only call __riscv_preserves function.}}
+}
+
+void invalid_out_in_preserves(void) __riscv_preserves("xsfmm") {
+ xsfmm_out_callee(); // expected-error {{conflicting attribute. Description: Function with __riscv_preserves can only call __riscv_preserves function.}}
+}
+
+void invalid_inout_in_preserves(void) __riscv_preserves("xsfmm") {
+ xsfmm_inout_callee(); // expected-error {{conflicting attribute. Description: Function with __riscv_preserves can only call __riscv_preserves function.}}
+}
+
+void invalid_new_in_preserves(void) __riscv_preserves("xsfmm") {
+ xsfmm_new_callee(); // expected-error {{conflicting attribute. Description: Function with __riscv_preserves can only call __riscv_preserves function.}}
+}
+
+void invalid_out_in_in(void) __riscv_in("xsfmm") {
+ xsfmm_out_callee(); // expected-error {{conflicting attribute. Description: Function with __riscv_in can only call __riscv_in and __riscv_preserves function.}}
+}
+
+void invalid_inout_in_in(void) __riscv_in("xsfmm") {
+ xsfmm_inout_callee(); // expected-error {{conflicting attribute. Description: Function with __riscv_in can only call __riscv_in and __riscv_preserves function.}}
+}
+
+void invalid_new_in_in(void) __riscv_in("xsfmm") {
+ xsfmm_new_callee(); // expected-error {{conflicting attribute. Description: Function with __riscv_in can only call __riscv_in and __riscv_preserves function.}}
+}
+
+void invalid_inout_in_out(void) __riscv_out("xsfmm") {
+ xsfmm_inout_callee(); // expected-error {{conflicting attribute. Description: Function with __riscv_out can only call __riscv_in, __riscv_out and __riscv_preserves function.}}
+}
+
+void invalid_new_in_out(void) __riscv_out("xsfmm") {
+ xsfmm_new_callee(); // expected-error {{conflicting attribute. Description: Function with __riscv_out can only call __riscv_in, __riscv_out and __riscv_preserves function.}}
+}
+
+void invalid_new_in_new(void) __riscv_new("xsfmm") {
+ xsfmm_new_callee(); // expected-error {{conflicting attribute. Description: __riscv_new and non-attributed function can not be called in attributed function.}}
+}
+
+void invalid_lib_function_intrinsic(void) __riscv_in("xsfmm") {
+ __builtin_memcpy(NULL, NULL, 0); // expected-error {{conflicting attribute. Description: Function with __riscv_in can only call __riscv_in and __riscv_preserves function.}}
+}
+
+void invalid_predefined_lib_function(void) __riscv_in("xsfmm") {
+ memcpy(NULL, NULL, 0); // expected-error {{conflicting attribute. Description: Function with __riscv_in can only call __riscv_in and __riscv_preserves function.}}
+ // expected-warning at -1 {{call to undeclared library function 'memcpy' with type 'void *(void *, const void *, __size_t)' (aka 'void *(void *, const void *, unsigned long)'); ISO C99 and later do not support implicit function declarations}}
+ // expected-note at -2 {{include the header <string.h> or explicitly provide a declaration for 'memcpy'}}
+}
>From 798abc343071f6f7259ac9f893f4f51fa2b97818 Mon Sep 17 00:00:00 2001
From: Brandon Wu <brandon.wu at sifive.com>
Date: Wed, 8 Jul 2026 19:25:39 -0700
Subject: [PATCH 2/2] fixup! wording
---
clang/include/clang/AST/TypeBase.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index 9654bdf34a196..96c66f67b797c 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -4909,7 +4909,7 @@ class FunctionType : public Type {
case RISCVNew:
return "__riscv_new";
}
- llvm_unreachable("Invalid RISCV state value");
+ llvm_unreachable("Invalid RISC-V state value");
}
static ArmStateValue getArmZAState(unsigned AttrBits) {
More information about the cfe-commits
mailing list