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 <[email protected]>
Date: Mon, 4 May 2026 15:27:57 +0900
Subject: [PATCH 1/5] [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@-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@-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 <[email protected]>
Date: Wed, 8 Jul 2026 19:25:39 -0700
Subject: [PATCH 2/5] 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) {

>From adf3a3697be13384f6bc0320b1ec8b10df1c1903 Mon Sep 17 00:00:00 2001
From: Brandon Wu <[email protected]>
Date: Sun, 12 Jul 2026 23:21:14 -0700
Subject: [PATCH 3/5] fixup! negative test

---
 clang/test/Sema/sifive-xsfmm-func-attr.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/clang/test/Sema/sifive-xsfmm-func-attr.c 
b/clang/test/Sema/sifive-xsfmm-func-attr.c
index c9f84b7847f47..afeb4f1a7d654 100644
--- a/clang/test/Sema/sifive-xsfmm-func-attr.c
+++ b/clang/test/Sema/sifive-xsfmm-func-attr.c
@@ -140,3 +140,6 @@ void invalid_predefined_lib_function(void) 
__riscv_in("xsfmm") {
   // expected-warning@-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@-2 {{include the header <string.h> or explicitly provide a 
declaration for 'memcpy'}}
 }
+
+void invalid_state(void) __riscv_new("12345") { // expected-error {{unknown 
state '12345'}}
+}

>From 5ecac9a82c504914718b7a8bb944d811571a6efd Mon Sep 17 00:00:00 2001
From: Brandon Wu <[email protected]>
Date: Wed, 12 Aug 2026 12:15:52 +0800
Subject: [PATCH 4/5] fixup! reject calls in attributed funcs

---
 clang/lib/CodeGen/CGCall.cpp                  | 23 +++++
 llvm/lib/Target/RISCV/CMakeLists.txt          |  1 +
 llvm/lib/Target/RISCV/RISCV.h                 |  3 +
 llvm/lib/Target/RISCV/RISCVInstrInfo.cpp      |  4 +
 llvm/lib/Target/RISCV/RISCVStateAttributes.h  | 29 ++++++
 llvm/lib/Target/RISCV/RISCVStateCheck.cpp     | 92 +++++++++++++++++++
 llvm/lib/Target/RISCV/RISCVTargetMachine.cpp  |  3 +
 .../Target/RISCV/RISCVTargetTransformInfo.cpp | 12 +++
 .../Target/RISCV/RISCVTargetTransformInfo.h   |  3 +
 llvm/test/CodeGen/RISCV/O0-pipeline.ll        |  1 +
 llvm/test/CodeGen/RISCV/O3-pipeline.ll        |  1 +
 llvm/test/CodeGen/RISCV/riscv-state-check.ll  | 30 ++++++
 12 files changed, 202 insertions(+)
 create mode 100644 llvm/lib/Target/RISCV/RISCVStateAttributes.h
 create mode 100644 llvm/lib/Target/RISCV/RISCVStateCheck.cpp
 create mode 100644 llvm/test/CodeGen/RISCV/riscv-state-check.ll

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 82b374e50fd41..abbe7efc4d6ad 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2045,6 +2045,29 @@ static void 
AddAttributesFromFunctionProtoType(ASTContext &Ctx,
     FuncAttrs.addAttribute("aarch64_out_zt0");
   if (FunctionType::getArmZT0State(SMEBits) == FunctionType::ARM_InOut)
     FuncAttrs.addAttribute("aarch64_inout_zt0");
+
+  unsigned RISCVBits = FPT->getRISCVAttributes();
+  switch (FunctionType::getRISCVXsfmmState(RISCVBits)) {
+  case FunctionType::RISCVNone:
+    break;
+  case FunctionType::RISCVIn:
+    FuncAttrs.addAttribute("riscv_in");
+    break;
+  case FunctionType::RISCVOut:
+    FuncAttrs.addAttribute("riscv_out");
+    break;
+  case FunctionType::RISCVInOut:
+    FuncAttrs.addAttribute("riscv_inout");
+    break;
+  case FunctionType::RISCVPreserves:
+    FuncAttrs.addAttribute("riscv_preserves");
+    break;
+  case FunctionType::RISCVNew:
+    FuncAttrs.addAttribute("riscv_new");
+    break;
+  default:
+    llvm_unreachable("Unimplemented RISC-V attribute type");
+  }
 }
 
 static void AddAttributesFromOMPAssumes(llvm::AttrBuilder &FuncAttrs,
diff --git a/llvm/lib/Target/RISCV/CMakeLists.txt 
b/llvm/lib/Target/RISCV/CMakeLists.txt
index 4a1a21cc9b5cd..03c96a722bde1 100644
--- a/llvm/lib/Target/RISCV/CMakeLists.txt
+++ b/llvm/lib/Target/RISCV/CMakeLists.txt
@@ -65,6 +65,7 @@ add_llvm_target(RISCVCodeGen
   RISCVRegisterInfo.cpp
   RISCVSelectionDAGInfo.cpp
   RISCVSubtarget.cpp
+  RISCVStateCheck.cpp
   RISCVTargetMachine.cpp
   RISCVTargetObjectFile.cpp
   RISCVTargetTransformInfo.cpp
diff --git a/llvm/lib/Target/RISCV/RISCV.h b/llvm/lib/Target/RISCV/RISCV.h
index 929a8d8f17b4f..6860599420ac7 100644
--- a/llvm/lib/Target/RISCV/RISCV.h
+++ b/llvm/lib/Target/RISCV/RISCV.h
@@ -134,6 +134,9 @@ FunctionPass *createRISCVVMV0EliminationPass();
 void initializeRISCVVMV0EliminationPass(PassRegistry &);
 
 void initializeRISCVAsmPrinterPass(PassRegistry &);
+
+FunctionPass *createRISCVStateCheckPass();
+void initializeRISCVStateCheckPass(PassRegistry &);
 } // namespace llvm
 
 #endif
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp 
b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
index b31a3a7760d3b..ab7cb0eb297a8 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
@@ -14,6 +14,7 @@
 #include "MCTargetDesc/RISCVBaseInfo.h"
 #include "MCTargetDesc/RISCVMatInt.h"
 #include "RISCV.h"
+#include "RISCVStateAttributes.h"
 #include "RISCVMachineFunctionInfo.h"
 #include "RISCVSubtarget.h"
 #include "llvm/ADT/STLExtras.h"
@@ -3625,6 +3626,9 @@ bool RISCVInstrInfo::isFunctionSafeToOutlineFrom(
   if (F.hasSection())
     return false;
 
+  if (RISCVState::hasAttribute(F))
+    return false;
+
   // It's safe to outline from MF.
   return true;
 }
diff --git a/llvm/lib/Target/RISCV/RISCVStateAttributes.h 
b/llvm/lib/Target/RISCV/RISCVStateAttributes.h
new file mode 100644
index 0000000000000..e755190b34156
--- /dev/null
+++ b/llvm/lib/Target/RISCV/RISCVStateAttributes.h
@@ -0,0 +1,29 @@
+//=-- RISCVStateAttributes.h - Helper for interpreting RISC-V attributes 
-*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_TARGET_RISCV_RISCVSTATEATTRIBUTES_H
+#define LLVM_LIB_TARGET_RISCV_RISCVSTATEATTRIBUTES_H
+
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/IR/Function.h"
+
+namespace llvm {
+namespace RISCVState {
+
+inline constexpr StringLiteral Attributes[] = {
+    "riscv_in", "riscv_out", "riscv_inout", "riscv_preserves", "riscv_new"};
+
+inline bool hasAttribute(const Function &F) {
+  return any_of(Attributes, [&F](StringRef A) { return F.hasFnAttribute(A); });
+}
+
+} // namespace RISCVState
+} // namespace llvm
+
+#endif // LLVM_LIB_TARGET_RISCV_RISCVSTATEATTRIBUTES_H
diff --git a/llvm/lib/Target/RISCV/RISCVStateCheck.cpp 
b/llvm/lib/Target/RISCV/RISCVStateCheck.cpp
new file mode 100644
index 0000000000000..c9ab99e39ac2b
--- /dev/null
+++ b/llvm/lib/Target/RISCV/RISCVStateCheck.cpp
@@ -0,0 +1,92 @@
+//======-- RISCVStateCheck.cpp - Helper for checking RISC-V attributes 
-======//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+
+#include "RISCV.h"
+#include "RISCVStateAttributes.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+
+using namespace llvm;
+
+#define RISCV_STATE_CHECK_NAME "RISC-V architecture state check"
+
+namespace {
+class RISCVStateCheck : public MachineFunctionPass {
+public:
+  static char ID;
+
+  RISCVStateCheck() : MachineFunctionPass(ID) {}
+
+  bool runOnMachineFunction(MachineFunction &MF) override;
+
+  void getAnalysisUsage(AnalysisUsage &AU) const override {
+    AU.setPreservesAll();
+    MachineFunctionPass::getAnalysisUsage(AU);
+  }
+
+  StringRef getPassName() const override { return RISCV_STATE_CHECK_NAME; }
+};
+} // namespace
+
+char RISCVStateCheck::ID = 0;
+
+INITIALIZE_PASS(RISCVStateCheck, "riscv-state-check",
+                RISCV_STATE_CHECK_NAME, false, true)
+
+static const MachineOperand *getCalleeSymbol(const MachineInstr &MI) {
+  for (const MachineOperand &MO : MI.operands())
+    if (MO.isGlobal() || MO.isSymbol())
+      return &MO;
+  return nullptr;
+}
+
+bool RISCVStateCheck::runOnMachineFunction(MachineFunction &MF) {
+  const Function &F = MF.getFunction();
+  if (!RISCVState::hasAttribute(F))
+    return false;
+
+  for (const MachineBasicBlock &MBB : MF) {
+    for (const MachineInstr &MI : MBB) {
+      if (!MI.isCall())
+        continue;
+
+      // There might be save/restore libcalls generated during frame lowering
+      // that only touch GPRs, in that case we can just skip it.
+      if (MI.getFlag(MachineInstr::FrameSetup) ||
+          MI.getFlag(MachineInstr::FrameDestroy))
+        continue;
+
+      const MachineOperand *Callee = getCalleeSymbol(MI);
+      if (!Callee)
+        continue;
+
+      std::string Name;
+      if (Callee->isSymbol()) {
+        Name = Callee->getSymbolName();
+      } else {
+        const GlobalValue *GV = Callee->getGlobal();
+        const auto *CalleeFn = dyn_cast<Function>(GV);
+        // Skip if this function is attributed which is already checked at
+        // frontend.
+        if (CalleeFn && RISCVState::hasAttribute(*CalleeFn))
+          continue;
+        Name = GV->getName().str();
+      }
+
+      std::string Message = "cannot emit call to '" + Name +
+                            "' from an RISC-V attributed function.";
+      reportFatalUsageError(MF.getName() + ": " + Message);
+    }
+  }
+
+  return false;
+}
+
+FunctionPass *llvm::createRISCVStateCheckPass() {
+  return new RISCVStateCheck();
+}
diff --git a/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp 
b/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp
index e13012a94711d..37ba888dcaabd 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp
@@ -145,6 +145,7 @@ extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void 
LLVMInitializeRISCVTarget() {
   initializeRISCVDAGToDAGISelLegacyPass(*PR);
   initializeRISCVMoveMergePass(*PR);
   initializeRISCVPushPopOptPass(*PR);
+  initializeRISCVStateCheckPass(*PR);
   initializeRISCVIndirectBranchTrackingPass(*PR);
   initializeRISCVLoadStoreOptPass(*PR);
   initializeRISCVPreAllocZilsdOptPass(*PR);
@@ -592,6 +593,8 @@ void RISCVPassConfig::addPreEmitPass() {
 }
 
 void RISCVPassConfig::addPreEmitPass2() {
+  addPass(createRISCVStateCheckPass());
+
   if (TM->getOptLevel() != CodeGenOptLevel::None) {
     addPass(createRISCVMoveMergePass());
     // Schedule PushPop Optimization before expansion of Pseudo instruction,
diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp 
b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
index 07a962f7dd03d..5a1f1c8260990 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
@@ -3701,6 +3701,18 @@ bool RISCVTTIImpl::shouldCopyAttributeWhenOutliningFrom(
   return BaseT::shouldCopyAttributeWhenOutliningFrom(Caller, Attr);
 }
 
+bool RISCVTTIImpl::areInlineCompatible(const Function *Caller,
+                                       const Function *Callee) const {
+  // riscv_new is the only function that can be called in an non-attributed
+  // function, we need to prevent inlining this kind of function in case any
+  // compiler generated non-attributed call in attributed function is inlined 
so
+  // it passes the check silently.
+  if (Callee->hasFnAttribute("riscv_new"))
+    return false;
+
+  return BaseT::areInlineCompatible(Caller, Callee);
+}
+
 std::optional<Instruction *>
 RISCVTTIImpl::instCombineIntrinsic(InstCombiner &IC, IntrinsicInst &II) const {
   // If all operands of a vmv.v.x are constant, fold a bitcast(vmv.v.x) to 
scale
diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h 
b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
index 2b43b93daa6c7..d8fa376c1f074 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
@@ -550,6 +550,9 @@ class RISCVTTIImpl final : public 
BasicTTIImplBase<RISCVTTIImpl> {
   shouldCopyAttributeWhenOutliningFrom(const Function *Caller,
                                        const Attribute &Attr) const override;
 
+  bool areInlineCompatible(const Function *Caller,
+                           const Function *Callee) const override;
+
   std::optional<Instruction *>
   instCombineIntrinsic(InstCombiner &IC, IntrinsicInst &II) const override;
 };
diff --git a/llvm/test/CodeGen/RISCV/O0-pipeline.ll 
b/llvm/test/CodeGen/RISCV/O0-pipeline.ll
index 847a8bd96c6d6..d8585bcf07e11 100644
--- a/llvm/test/CodeGen/RISCV/O0-pipeline.ll
+++ b/llvm/test/CodeGen/RISCV/O0-pipeline.ll
@@ -75,6 +75,7 @@
 ; CHECK-NEXT:       Lazy Machine Block Frequency Analysis
 ; CHECK-NEXT:       Machine Optimization Remark Emitter
 ; CHECK-NEXT:       Stack Frame Layout Analysis
+; CHECK-NEXT:       RISC-V architecture state check
 ; CHECK-NEXT:       RISC-V pseudo instruction expansion pass
 ; CHECK-NEXT:       RISC-V atomic pseudo instruction expansion pass
 ; CHECK-NEXT:       Unpack machine instruction bundles
diff --git a/llvm/test/CodeGen/RISCV/O3-pipeline.ll 
b/llvm/test/CodeGen/RISCV/O3-pipeline.ll
index 149764ffedf9e..8c1fc4de50730 100644
--- a/llvm/test/CodeGen/RISCV/O3-pipeline.ll
+++ b/llvm/test/CodeGen/RISCV/O3-pipeline.ll
@@ -219,6 +219,7 @@
 ; CHECK-NEXT:       Lazy Machine Block Frequency Analysis
 ; CHECK-NEXT:       Machine Optimization Remark Emitter
 ; CHECK-NEXT:       Stack Frame Layout Analysis
+; CHECK-NEXT:       RISC-V architecture state check
 ; CHECK-NEXT:       RISC-V Zcmp move merging pass
 ; CHECK-NEXT:       RISC-V Zcmp Push/Pop optimization pass
 ; CHECK-NEXT:       RISC-V pseudo instruction expansion pass
diff --git a/llvm/test/CodeGen/RISCV/riscv-state-check.ll 
b/llvm/test/CodeGen/RISCV/riscv-state-check.ll
new file mode 100644
index 0000000000000..660767d2bcc4b
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/riscv-state-check.ll
@@ -0,0 +1,30 @@
+; RUN: not llc -mtriple=riscv64 -mattr=+xsfmmbase,+save-restore -o /dev/null < 
%s 2>&1 \
+; RUN:   | FileCheck %s --implicit-check-not=error:
+
+declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1)
+; CHECK: error: libgcc_call: cannot emit call to '__divdi3' from an RISC-V 
attributed function.
+define i64 @libgcc_call(i64 %a, i64 %b) "riscv_inout" {
+  %d = sdiv i64 %a, %b
+  ret i64 %d
+}
+
+; CHECK: error: memcpy_call: cannot emit call to 'memcpy' from an RISC-V 
attributed function.
+define void @memcpy_call(ptr %d, ptr %s) "riscv_in" {
+  call void @llvm.memcpy.p0.p0.i64(ptr %d, ptr %s, i64 1024, i1 false)
+  ret void
+}
+
+declare void @extern_func()
+; CHECK: error: extern_call: cannot emit call to 'extern_func' from an RISC-V 
attributed function.
+define void @extern_call() "riscv_in" {
+  tail call void @extern_func()
+  ret void
+}
+
+declare void @preserves(i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, i64, 
i64) "riscv_preserves"
+; spill lib calls should be legal, e.g. __riscv_save_4, __riscv_save_5
+define void @legal(i64 %a) "riscv_in" {
+  call void @preserves(i64 %a, i64 1, i64 2, i64 3, i64 4, i64 5, i64 6, i64 
7, i64 8, i64 9, i64 10, i64 11)
+  call void @preserves(i64 %a, i64 1, i64 2, i64 3, i64 4, i64 5, i64 6, i64 
7, i64 8, i64 9, i64 10, i64 11)
+  ret void
+}

>From 35c4b0f030155f9b605a558eb59c764b6d65d23d Mon Sep 17 00:00:00 2001
From: Brandon Wu <[email protected]>
Date: Mon, 17 Aug 2026 12:41:59 +0800
Subject: [PATCH 5/5] fixup! clang format

---
 llvm/lib/Target/RISCV/RISCVInstrInfo.cpp  | 2 +-
 llvm/lib/Target/RISCV/RISCVStateCheck.cpp | 5 ++---
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp 
b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
index ab7cb0eb297a8..f7a1c100e3132 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
@@ -14,8 +14,8 @@
 #include "MCTargetDesc/RISCVBaseInfo.h"
 #include "MCTargetDesc/RISCVMatInt.h"
 #include "RISCV.h"
-#include "RISCVStateAttributes.h"
 #include "RISCVMachineFunctionInfo.h"
+#include "RISCVStateAttributes.h"
 #include "RISCVSubtarget.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
diff --git a/llvm/lib/Target/RISCV/RISCVStateCheck.cpp 
b/llvm/lib/Target/RISCV/RISCVStateCheck.cpp
index c9ab99e39ac2b..d8450786c4107 100644
--- a/llvm/lib/Target/RISCV/RISCVStateCheck.cpp
+++ b/llvm/lib/Target/RISCV/RISCVStateCheck.cpp
@@ -6,7 +6,6 @@
 //
 
//===----------------------------------------------------------------------===//
 
-
 #include "RISCV.h"
 #include "RISCVStateAttributes.h"
 #include "llvm/CodeGen/MachineFunctionPass.h"
@@ -35,8 +34,8 @@ class RISCVStateCheck : public MachineFunctionPass {
 
 char RISCVStateCheck::ID = 0;
 
-INITIALIZE_PASS(RISCVStateCheck, "riscv-state-check",
-                RISCV_STATE_CHECK_NAME, false, true)
+INITIALIZE_PASS(RISCVStateCheck, "riscv-state-check", RISCV_STATE_CHECK_NAME,
+                false, true)
 
 static const MachineOperand *getCalleeSymbol(const MachineInstr &MI) {
   for (const MachineOperand &MO : MI.operands())

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to