[llvm] [CodeGen][AMDGPU] Support type-aware intrinsic feature checks (PR #205536)

Keshav Vinayak Jha via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 22 05:37:31 PDT 2026


https://github.com/keshavvinayak01 updated https://github.com/llvm/llvm-project/pull/205536

>From 83e9fd9cd51860f594a6a6af8ba10c1132084b6f Mon Sep 17 00:00:00 2001
From: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
Date: Wed, 1 Jul 2026 00:06:56 +0530
Subject: [PATCH 1/7] [CodeGen] Allow intrinsic feature checks to inspect
 overload types

Extend the intrinsic subtarget feature check to receive the overloaded intrinsic function type, so targets can make feature requirements depend on the selected overload.

AMDGPU uses this for llvm.amdgcn.ballot: the i32 overload now requires wavefrontsize32, while the i64 overload remains valid on wave64 targets. This reports a clean unsupported-intrinsic diagnostic instead of reaching instruction selection for an invalid i32 ballot on wave64.

Tests cover rejecting ballot.i32 on wave64 without rejecting the valid ballot overload and wave-size combinations.

Co-authored-by: GPT-5 <noreply at openai.com>
Signed-off-by: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
---
 .../llvm/CodeGen/TargetSubtargetInfo.h        |  9 ++++++
 llvm/include/llvm/IR/DiagnosticInfo.h         |  3 +-
 llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp  | 20 +++++++++----
 .../SelectionDAG/SelectionDAGBuilder.cpp      |  8 +++--
 llvm/lib/CodeGen/TargetSubtargetInfo.cpp      | 13 +++++++++
 llvm/lib/IR/DiagnosticInfo.cpp                |  9 ++++--
 llvm/lib/Target/AMDGPU/GCNSubtarget.cpp       | 12 ++++++++
 llvm/lib/Target/AMDGPU/GCNSubtarget.h         |  4 +++
 .../AMDGPU/llvm.amdgcn.ballot.wave-size.ll    | 29 +++++++++++++++++++
 9 files changed, 95 insertions(+), 12 deletions(-)
 create mode 100644 llvm/test/CodeGen/AMDGPU/llvm.amdgcn.ballot.wave-size.ll

diff --git a/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h b/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
index db73a9675b71d..12cb09874e50b 100644
--- a/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
+++ b/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
@@ -31,6 +31,7 @@
 namespace llvm {
 
 class APInt;
+class FunctionType;
 class MachineFunction;
 class ScheduleDAGMutation;
 class CallLowering;
@@ -94,6 +95,14 @@ class LLVM_ABI TargetSubtargetInfo : public MCSubtargetInfo {
   /// \returns true if the target intrinsic \p IntrinsicID is supported by this
   /// subtarget.
   bool isIntrinsicSupported(unsigned IntrinsicID) const;
+  bool isIntrinsicSupported(unsigned IntrinsicID,
+                            const FunctionType *FTy) const;
+
+  /// \returns the target features required by the target intrinsic
+  /// \p IntrinsicID with signature \p FTy.
+  virtual StringRef
+  getRequiredTargetFeaturesForIntrinsic(unsigned IntrinsicID,
+                                        const FunctionType *FTy) const;
 
   // Interfaces to the major aspects of target machine information:
   //
diff --git a/llvm/include/llvm/IR/DiagnosticInfo.h b/llvm/include/llvm/IR/DiagnosticInfo.h
index da62b62bd8c74..2b67d83892961 100644
--- a/llvm/include/llvm/IR/DiagnosticInfo.h
+++ b/llvm/include/llvm/IR/DiagnosticInfo.h
@@ -1138,7 +1138,8 @@ class LLVM_ABI DiagnosticInfoUnsupportedTargetIntrinsic
 public:
   DiagnosticInfoUnsupportedTargetIntrinsic(
       const Function &Fn, unsigned IntrinsicID,
-      const DiagnosticLocation &Loc = DiagnosticLocation());
+      const DiagnosticLocation &Loc = DiagnosticLocation(),
+      StringRef RequiredFeatures = {});
 
   static bool classof(const DiagnosticInfo *DI) {
     return DI->getKind() == DK_UnsupportedTargetIntrinsic;
diff --git a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
index c66226258c87e..1a5742f1c721f 100644
--- a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
@@ -2878,10 +2878,14 @@ bool IRTranslator::translateCall(const User &U, MachineIRBuilder &MIRBuilder) {
 
   assert(ID != Intrinsic::not_intrinsic && "unknown intrinsic");
 
-  if (!MF->getSubtarget().isIntrinsicSupported(ID)) {
+  if (!MF->getSubtarget().isIntrinsicSupported(ID, CI.getFunctionType())) {
     const Function &Fn = MF->getFunction();
-    Fn.getContext().diagnose(
-        DiagnosticInfoUnsupportedTargetIntrinsic(Fn, ID, CI.getDebugLoc()));
+    StringRef RequiredFeatures =
+        MF->getSubtarget().getRequiredTargetFeaturesForIntrinsic(
+            ID, CI.getFunctionType());
+    Fn.getContext().diagnose(DiagnosticInfoUnsupportedTargetIntrinsic(
+        Fn, ID, CI.getDebugLoc(), RequiredFeatures));
+    return false;
   }
 
   if (translateKnownIntrinsic(CI, ID, MIRBuilder))
@@ -2897,10 +2901,14 @@ bool IRTranslator::translateCall(const User &U, MachineIRBuilder &MIRBuilder) {
 bool IRTranslator::translateIntrinsic(
     const CallBase &CB, Intrinsic::ID ID, MachineIRBuilder &MIRBuilder,
     ArrayRef<TargetLowering::IntrinsicInfo> TgtMemIntrinsicInfos) {
-  if (!MF->getSubtarget().isIntrinsicSupported(ID)) {
+  if (!MF->getSubtarget().isIntrinsicSupported(ID, CB.getFunctionType())) {
     const Function &F = MF->getFunction();
-    F.getContext().diagnose(
-        DiagnosticInfoUnsupportedTargetIntrinsic(F, ID, CB.getDebugLoc()));
+    StringRef RequiredFeatures =
+        MF->getSubtarget().getRequiredTargetFeaturesForIntrinsic(
+            ID, CB.getFunctionType());
+    F.getContext().diagnose(DiagnosticInfoUnsupportedTargetIntrinsic(
+        F, ID, CB.getDebugLoc(), RequiredFeatures));
+    return false;
   }
 
   ArrayRef<Register> ResultRegs;
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index effa4a8d5f1b9..37c78825f6458 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -5565,10 +5565,14 @@ void SelectionDAGBuilder::visitTargetIntrinsic(const CallInst &I,
   Intrinsic::ID IntrinsicID = static_cast<Intrinsic::ID>(Intrinsic);
 
   if (!DAG.getMachineFunction().getSubtarget().isIntrinsicSupported(
-          Intrinsic)) {
+          Intrinsic, I.getFunctionType())) {
     SDLoc DL = getCurSDLoc();
+    StringRef RequiredFeatures = DAG.getMachineFunction()
+                                     .getSubtarget()
+                                     .getRequiredTargetFeaturesForIntrinsic(
+                                         Intrinsic, I.getFunctionType());
     DAG.getContext()->diagnose(DiagnosticInfoUnsupportedTargetIntrinsic(
-        *I.getFunction(), IntrinsicID, DL.getDebugLoc()));
+        *I.getFunction(), IntrinsicID, DL.getDebugLoc(), RequiredFeatures));
 
     // The intrinsic is not available on this subtarget. Preserve the chain for
     // side-effecting intrinsics and lower any result to poison so that
diff --git a/llvm/lib/CodeGen/TargetSubtargetInfo.cpp b/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
index 727eff7ddce58..139f3dd1f908f 100644
--- a/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
+++ b/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
@@ -40,6 +40,19 @@ bool TargetSubtargetInfo::isIntrinsicSupported(unsigned IntrinsicID) const {
   return It->second;
 }
 
+bool TargetSubtargetInfo::isIntrinsicSupported(unsigned IntrinsicID,
+                                               const FunctionType *FTy) const {
+  StringRef RequiredFeatures =
+      getRequiredTargetFeaturesForIntrinsic(IntrinsicID, FTy);
+  return RequiredFeatures.empty() || checkFeatureExpression(RequiredFeatures);
+}
+
+StringRef TargetSubtargetInfo::getRequiredTargetFeaturesForIntrinsic(
+    unsigned IntrinsicID, const FunctionType *FTy) const {
+  return Intrinsic::getRequiredTargetFeatures(
+      static_cast<Intrinsic::ID>(IntrinsicID));
+}
+
 bool TargetSubtargetInfo::enableAtomicExpand() const {
   return true;
 }
diff --git a/llvm/lib/IR/DiagnosticInfo.cpp b/llvm/lib/IR/DiagnosticInfo.cpp
index a24b6d0935008..bcea525825aea 100644
--- a/llvm/lib/IR/DiagnosticInfo.cpp
+++ b/llvm/lib/IR/DiagnosticInfo.cpp
@@ -422,12 +422,15 @@ void DiagnosticInfoUnsupported::print(DiagnosticPrinter &DP) const {
 DiagnosticInfoUnsupportedTargetIntrinsic::
     DiagnosticInfoUnsupportedTargetIntrinsic(const Function &Fn,
                                              unsigned IntrinsicID,
-                                             const DiagnosticLocation &Loc)
+                                             const DiagnosticLocation &Loc,
+                                             StringRef RequiredFeaturesOverride)
     : DiagnosticInfoWithLocationBase(DK_UnsupportedTargetIntrinsic, DS_Error,
                                      Fn, Loc),
       IntrinsicID(IntrinsicID),
-      RequiredFeatures(Intrinsic::getRequiredTargetFeatures(
-          static_cast<Intrinsic::ID>(IntrinsicID))) {
+      RequiredFeatures(RequiredFeaturesOverride.empty()
+                           ? Intrinsic::getRequiredTargetFeatures(
+                                 static_cast<Intrinsic::ID>(IntrinsicID))
+                           : RequiredFeaturesOverride) {
   assert(!RequiredFeatures.empty() &&
          "intrinsic without required features should be supported");
 }
diff --git a/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp b/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp
index 1c0e718bd8d97..6e8e1bfc34234 100644
--- a/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp
+++ b/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp
@@ -24,7 +24,9 @@
 #include "llvm/CodeGen/GlobalISel/InlineAsmLowering.h"
 #include "llvm/CodeGen/MachineScheduler.h"
 #include "llvm/CodeGen/TargetFrameLowering.h"
+#include "llvm/IR/DerivedTypes.h"
 #include "llvm/IR/DiagnosticInfo.h"
+#include "llvm/IR/IntrinsicsAMDGPU.h"
 #include "llvm/IR/MDBuilder.h"
 #include "llvm/TargetParser/AMDGPUTargetParser.h"
 #include <algorithm>
@@ -55,6 +57,16 @@ static cl::opt<unsigned>
 
 GCNSubtarget::~GCNSubtarget() = default;
 
+StringRef GCNSubtarget::getRequiredTargetFeaturesForIntrinsic(
+    unsigned IntrinsicID, const FunctionType *FTy) const {
+  if (FTy && IntrinsicID == Intrinsic::amdgcn_ballot &&
+      FTy->getReturnType()->isIntegerTy(32))
+    return "wavefrontsize32";
+
+  return TargetSubtargetInfo::getRequiredTargetFeaturesForIntrinsic(IntrinsicID,
+                                                                    FTy);
+}
+
 static AMDGPUSubtarget::Generation computeDefaultGeneration(const Triple &TT) {
   // Legacy triples without a subarch default to the first target that supports
   // flat addressing for HSA, otherwise the first amdgcn target.
diff --git a/llvm/lib/Target/AMDGPU/GCNSubtarget.h b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
index 0459ab2bc85e1..12dd3f6b31f93 100644
--- a/llvm/lib/Target/AMDGPU/GCNSubtarget.h
+++ b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
@@ -125,6 +125,10 @@ class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
   /// function \p F.
   void checkSubtargetFeatures(const Function &F) const;
 
+  StringRef
+  getRequiredTargetFeaturesForIntrinsic(unsigned IntrinsicID,
+                                        const FunctionType *FTy) const override;
+
   const SIInstrInfo *getInstrInfo() const override { return &InstrInfo; }
 
   const SIFrameLowering *getFrameLowering() const override {
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.ballot.wave-size.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.ballot.wave-size.ll
new file mode 100644
index 0000000000000..67e406272d1b6
--- /dev/null
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.ballot.wave-size.ll
@@ -0,0 +1,29 @@
+; RUN: split-file %s %t
+; RUN: not llc -global-isel=0 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 -filetype=null %t/ballot-i32.ll 2>&1 | FileCheck %s --check-prefix=ERR
+; RUN: not llc -global-isel=1 -global-isel-abort=0 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 -filetype=null %t/ballot-i32.ll 2>&1 | FileCheck %s --check-prefix=ERR
+; RUN: llc -global-isel=0 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 -filetype=null %t/ballot-i64.ll
+; RUN: llc -global-isel=1 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 -filetype=null %t/ballot-i64.ll
+; RUN: llc -global-isel=0 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx1010 -filetype=null %t/ballot-i32.ll
+; RUN: llc -global-isel=1 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx1010 -filetype=null %t/ballot-i32.ll
+
+; ERR: error: {{.*}}: in function {{@?}}ballot_i32{{.*}}: llvm.amdgcn.ballot requires target feature 'wavefrontsize32'
+
+;--- ballot-i32.ll
+declare i32 @llvm.amdgcn.ballot.i32(i1)
+
+define amdgpu_kernel void @ballot_i32(i32 %x, ptr addrspace(1) %out) {
+  %trunc = trunc i32 %x to i1
+  %ballot = call i32 @llvm.amdgcn.ballot.i32(i1 %trunc)
+  store i32 %ballot, ptr addrspace(1) %out
+  ret void
+}
+
+;--- ballot-i64.ll
+declare i64 @llvm.amdgcn.ballot.i64(i1)
+
+define amdgpu_kernel void @ballot_i64(i32 %x, ptr addrspace(1) %out) {
+  %trunc = trunc i32 %x to i1
+  %ballot = call i64 @llvm.amdgcn.ballot.i64(i1 %trunc)
+  store i64 %ballot, ptr addrspace(1) %out
+  ret void
+}

>From d68af337c53529e87d70488042391774e20ed818 Mon Sep 17 00:00:00 2001
From: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
Date: Wed, 1 Jul 2026 00:58:00 +0530
Subject: [PATCH 2/7] [CodeGen] Simplify unsupported intrinsic diagnostics

Require callers to pass the resolved required feature string to DiagnosticInfoUnsupportedTargetIntrinsic instead of keeping a fallback path that only sees the intrinsic ID.

Also document why overload-sensitive intrinsic feature checks receive the resolved FunctionType.

Co-authored-by: GPT-5 <noreply at openai.com>
Signed-off-by: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
---
 llvm/include/llvm/CodeGen/TargetSubtargetInfo.h | 3 +++
 llvm/include/llvm/IR/DiagnosticInfo.h           | 8 ++++----
 llvm/lib/IR/DiagnosticInfo.cpp                  | 8 ++------
 3 files changed, 9 insertions(+), 10 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h b/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
index 12cb09874e50b..fd92130bbe0d9 100644
--- a/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
+++ b/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
@@ -100,6 +100,9 @@ class LLVM_ABI TargetSubtargetInfo : public MCSubtargetInfo {
 
   /// \returns the target features required by the target intrinsic
   /// \p IntrinsicID with signature \p FTy.
+  ///
+  /// The intrinsic TargetFeatures table is keyed by intrinsic ID. Targets can
+  /// override this when support also depends on the resolved overload type.
   virtual StringRef
   getRequiredTargetFeaturesForIntrinsic(unsigned IntrinsicID,
                                         const FunctionType *FTy) const;
diff --git a/llvm/include/llvm/IR/DiagnosticInfo.h b/llvm/include/llvm/IR/DiagnosticInfo.h
index 2b67d83892961..0cd7299ae6852 100644
--- a/llvm/include/llvm/IR/DiagnosticInfo.h
+++ b/llvm/include/llvm/IR/DiagnosticInfo.h
@@ -1136,10 +1136,10 @@ class LLVM_ABI DiagnosticInfoUnsupportedTargetIntrinsic
   StringRef RequiredFeatures;
 
 public:
-  DiagnosticInfoUnsupportedTargetIntrinsic(
-      const Function &Fn, unsigned IntrinsicID,
-      const DiagnosticLocation &Loc = DiagnosticLocation(),
-      StringRef RequiredFeatures = {});
+  DiagnosticInfoUnsupportedTargetIntrinsic(const Function &Fn,
+                                           unsigned IntrinsicID,
+                                           const DiagnosticLocation &Loc,
+                                           StringRef RequiredFeatures);
 
   static bool classof(const DiagnosticInfo *DI) {
     return DI->getKind() == DK_UnsupportedTargetIntrinsic;
diff --git a/llvm/lib/IR/DiagnosticInfo.cpp b/llvm/lib/IR/DiagnosticInfo.cpp
index bcea525825aea..037fd16064158 100644
--- a/llvm/lib/IR/DiagnosticInfo.cpp
+++ b/llvm/lib/IR/DiagnosticInfo.cpp
@@ -423,14 +423,10 @@ DiagnosticInfoUnsupportedTargetIntrinsic::
     DiagnosticInfoUnsupportedTargetIntrinsic(const Function &Fn,
                                              unsigned IntrinsicID,
                                              const DiagnosticLocation &Loc,
-                                             StringRef RequiredFeaturesOverride)
+                                             StringRef RequiredFeatures)
     : DiagnosticInfoWithLocationBase(DK_UnsupportedTargetIntrinsic, DS_Error,
                                      Fn, Loc),
-      IntrinsicID(IntrinsicID),
-      RequiredFeatures(RequiredFeaturesOverride.empty()
-                           ? Intrinsic::getRequiredTargetFeatures(
-                                 static_cast<Intrinsic::ID>(IntrinsicID))
-                           : RequiredFeaturesOverride) {
+      IntrinsicID(IntrinsicID), RequiredFeatures(RequiredFeatures) {
   assert(!RequiredFeatures.empty() &&
          "intrinsic without required features should be supported");
 }

>From 230549b46b20b3303b67ca62f7be16eabc925b97 Mon Sep 17 00:00:00 2001
From: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
Date: Wed, 1 Jul 2026 20:04:42 +0530
Subject: [PATCH 3/7] [CodeGen] Include intrinsic overload name in diagnostics

Preserve the called intrinsic declaration name in unsupported-target-intrinsic diagnostics so overloaded intrinsics identify the rejected type in the error message.

Update the folded AMDGPU ballot checks to expect the i32 overload name in the wave64 diagnostic.

Co-authored-by: GPT-5 <noreply at openai.com>
Signed-off-by: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
---
 llvm/include/llvm/IR/DiagnosticInfo.h         |  3 ++
 llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp  |  7 +++--
 .../SelectionDAG/SelectionDAGBuilder.cpp      |  6 +++-
 llvm/lib/IR/DiagnosticInfo.cpp                | 10 ++++---
 .../GlobalISel/llvm.amdgcn.ballot.i32.ll      |  3 ++
 .../CodeGen/AMDGPU/llvm.amdgcn.ballot.i32.ll  |  3 ++
 .../AMDGPU/llvm.amdgcn.ballot.wave-size.ll    | 29 -------------------
 7 files changed, 25 insertions(+), 36 deletions(-)
 delete mode 100644 llvm/test/CodeGen/AMDGPU/llvm.amdgcn.ballot.wave-size.ll

diff --git a/llvm/include/llvm/IR/DiagnosticInfo.h b/llvm/include/llvm/IR/DiagnosticInfo.h
index 0cd7299ae6852..4067d267df5b9 100644
--- a/llvm/include/llvm/IR/DiagnosticInfo.h
+++ b/llvm/include/llvm/IR/DiagnosticInfo.h
@@ -1133,11 +1133,13 @@ class LLVM_ABI DiagnosticInfoUnsupportedTargetIntrinsic
     : public DiagnosticInfoWithLocationBase {
 private:
   unsigned IntrinsicID;
+  std::string IntrinsicName;
   StringRef RequiredFeatures;
 
 public:
   DiagnosticInfoUnsupportedTargetIntrinsic(const Function &Fn,
                                            unsigned IntrinsicID,
+                                           StringRef IntrinsicName,
                                            const DiagnosticLocation &Loc,
                                            StringRef RequiredFeatures);
 
@@ -1146,6 +1148,7 @@ class LLVM_ABI DiagnosticInfoUnsupportedTargetIntrinsic
   }
 
   unsigned getIntrinsicID() const { return IntrinsicID; }
+  StringRef getIntrinsicName() const { return IntrinsicName; }
   StringRef getRequiredFeatures() const { return RequiredFeatures; }
   std::string getMessage() const;
 
diff --git a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
index 1a5742f1c721f..f422497aa27a9 100644
--- a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
@@ -2884,7 +2884,7 @@ bool IRTranslator::translateCall(const User &U, MachineIRBuilder &MIRBuilder) {
         MF->getSubtarget().getRequiredTargetFeaturesForIntrinsic(
             ID, CI.getFunctionType());
     Fn.getContext().diagnose(DiagnosticInfoUnsupportedTargetIntrinsic(
-        Fn, ID, CI.getDebugLoc(), RequiredFeatures));
+        Fn, ID, F->getName(), CI.getDebugLoc(), RequiredFeatures));
     return false;
   }
 
@@ -2906,8 +2906,11 @@ bool IRTranslator::translateIntrinsic(
     StringRef RequiredFeatures =
         MF->getSubtarget().getRequiredTargetFeaturesForIntrinsic(
             ID, CB.getFunctionType());
+    const Function *IntrinsicFn = CB.getCalledFunction();
+    StringRef IntrinsicName =
+        IntrinsicFn ? IntrinsicFn->getName() : Intrinsic::getBaseName(ID);
     F.getContext().diagnose(DiagnosticInfoUnsupportedTargetIntrinsic(
-        F, ID, CB.getDebugLoc(), RequiredFeatures));
+        F, ID, IntrinsicName, CB.getDebugLoc(), RequiredFeatures));
     return false;
   }
 
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index 37c78825f6458..2a40a7c8403b5 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -5571,8 +5571,12 @@ void SelectionDAGBuilder::visitTargetIntrinsic(const CallInst &I,
                                      .getSubtarget()
                                      .getRequiredTargetFeaturesForIntrinsic(
                                          Intrinsic, I.getFunctionType());
+    const Function *IntrinsicFn = I.getCalledFunction();
+    StringRef IntrinsicName = IntrinsicFn ? IntrinsicFn->getName()
+                                          : Intrinsic::getBaseName(IntrinsicID);
     DAG.getContext()->diagnose(DiagnosticInfoUnsupportedTargetIntrinsic(
-        *I.getFunction(), IntrinsicID, DL.getDebugLoc(), RequiredFeatures));
+        *I.getFunction(), IntrinsicID, IntrinsicName, DL.getDebugLoc(),
+        RequiredFeatures));
 
     // The intrinsic is not available on this subtarget. Preserve the chain for
     // side-effecting intrinsics and lower any result to poison so that
diff --git a/llvm/lib/IR/DiagnosticInfo.cpp b/llvm/lib/IR/DiagnosticInfo.cpp
index 037fd16064158..569e0e0c1374e 100644
--- a/llvm/lib/IR/DiagnosticInfo.cpp
+++ b/llvm/lib/IR/DiagnosticInfo.cpp
@@ -422,19 +422,21 @@ void DiagnosticInfoUnsupported::print(DiagnosticPrinter &DP) const {
 DiagnosticInfoUnsupportedTargetIntrinsic::
     DiagnosticInfoUnsupportedTargetIntrinsic(const Function &Fn,
                                              unsigned IntrinsicID,
+                                             StringRef IntrinsicName,
                                              const DiagnosticLocation &Loc,
                                              StringRef RequiredFeatures)
     : DiagnosticInfoWithLocationBase(DK_UnsupportedTargetIntrinsic, DS_Error,
                                      Fn, Loc),
-      IntrinsicID(IntrinsicID), RequiredFeatures(RequiredFeatures) {
+      IntrinsicID(IntrinsicID), IntrinsicName(IntrinsicName),
+      RequiredFeatures(RequiredFeatures) {
   assert(!RequiredFeatures.empty() &&
          "intrinsic without required features should be supported");
+  assert(!IntrinsicName.empty() && "intrinsic name should not be empty");
 }
 
 std::string DiagnosticInfoUnsupportedTargetIntrinsic::getMessage() const {
-  return (Twine(
-              Intrinsic::getBaseName(static_cast<Intrinsic::ID>(IntrinsicID))) +
-          " requires target feature '" + RequiredFeatures + "'")
+  return (Twine(IntrinsicName) + " requires target feature '" +
+          RequiredFeatures + "'")
       .str();
 }
 
diff --git a/llvm/test/CodeGen/AMDGPU/GlobalISel/llvm.amdgcn.ballot.i32.ll b/llvm/test/CodeGen/AMDGPU/GlobalISel/llvm.amdgcn.ballot.i32.ll
index c1742bff4b06f..af1a2eab576e5 100644
--- a/llvm/test/CodeGen/AMDGPU/GlobalISel/llvm.amdgcn.ballot.i32.ll
+++ b/llvm/test/CodeGen/AMDGPU/GlobalISel/llvm.amdgcn.ballot.i32.ll
@@ -2,6 +2,9 @@
 ; RUN: llc -mtriple=amdgpu10.10 -global-isel < %s | FileCheck -check-prefixes=CHECK,GFX10 %s
 ; RUN: llc -mtriple=amdgpu11.00 -mattr=+real-true16 -amdgpu-enable-delay-alu=0 -global-isel < %s | FileCheck -check-prefixes=CHECK,GFX11,GFX11-TRUE16 %s
 ; RUN: llc -mtriple=amdgpu11.00 -mattr=-real-true16 -amdgpu-enable-delay-alu=0 -global-isel < %s | FileCheck -check-prefixes=CHECK,GFX11,GFX11-FAKE16 %s
+; RUN: llvm-extract --func=constant_false -S %s | not llc -mtriple=amdgpu9.00 -global-isel -global-isel-abort=0 -filetype=null 2>&1 | FileCheck -check-prefix=ERR %s
+
+; ERR: error: {{.*}}: in function {{@?}}constant_false{{.*}}: llvm.amdgcn.ballot.i32 requires target feature 'wavefrontsize32'
 
 declare i32 @llvm.amdgcn.ballot.i32(i1)
 declare i32 @llvm.ctpop.i32(i32)
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.ballot.i32.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.ballot.i32.ll
index fb5b9d72d06e5..6063963c1d614 100644
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.ballot.i32.ll
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.ballot.i32.ll
@@ -1,6 +1,9 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
 ; RUN: llc -mtriple=amdgpu10.10 -mattr=+wavefrontsize32 < %s | FileCheck -check-prefixes=CHECK,GFX10 %s
 ; RUN: llc -mtriple=amdgpu11.00 -amdgpu-enable-delay-alu=0 -mattr=+wavefrontsize32 < %s | FileCheck -check-prefixes=CHECK,GFX11 %s
+; RUN: llvm-extract --func=constant_false -S %s | not llc -mtriple=amdgpu9.00 -filetype=null 2>&1 | FileCheck -check-prefix=ERR %s
+
+; ERR: error: {{.*}}: in function {{@?}}constant_false{{.*}}: llvm.amdgcn.ballot.i32 requires target feature 'wavefrontsize32'
 
 declare i32 @llvm.amdgcn.ballot.i32(i1)
 declare i32 @llvm.ctpop.i32(i32)
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.ballot.wave-size.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.ballot.wave-size.ll
deleted file mode 100644
index 67e406272d1b6..0000000000000
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.ballot.wave-size.ll
+++ /dev/null
@@ -1,29 +0,0 @@
-; RUN: split-file %s %t
-; RUN: not llc -global-isel=0 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 -filetype=null %t/ballot-i32.ll 2>&1 | FileCheck %s --check-prefix=ERR
-; RUN: not llc -global-isel=1 -global-isel-abort=0 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 -filetype=null %t/ballot-i32.ll 2>&1 | FileCheck %s --check-prefix=ERR
-; RUN: llc -global-isel=0 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 -filetype=null %t/ballot-i64.ll
-; RUN: llc -global-isel=1 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx900 -filetype=null %t/ballot-i64.ll
-; RUN: llc -global-isel=0 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx1010 -filetype=null %t/ballot-i32.ll
-; RUN: llc -global-isel=1 -mtriple=amdgcn-amd-amdhsa -mcpu=gfx1010 -filetype=null %t/ballot-i32.ll
-
-; ERR: error: {{.*}}: in function {{@?}}ballot_i32{{.*}}: llvm.amdgcn.ballot requires target feature 'wavefrontsize32'
-
-;--- ballot-i32.ll
-declare i32 @llvm.amdgcn.ballot.i32(i1)
-
-define amdgpu_kernel void @ballot_i32(i32 %x, ptr addrspace(1) %out) {
-  %trunc = trunc i32 %x to i1
-  %ballot = call i32 @llvm.amdgcn.ballot.i32(i1 %trunc)
-  store i32 %ballot, ptr addrspace(1) %out
-  ret void
-}
-
-;--- ballot-i64.ll
-declare i64 @llvm.amdgcn.ballot.i64(i1)
-
-define amdgpu_kernel void @ballot_i64(i32 %x, ptr addrspace(1) %out) {
-  %trunc = trunc i32 %x to i1
-  %ballot = call i64 @llvm.amdgcn.ballot.i64(i1 %trunc)
-  store i64 %ballot, ptr addrspace(1) %out
-  ret void
-}

>From 92dbb28b57b0cff87bbc3286e727ae9ddb21b832 Mon Sep 17 00:00:00 2001
From: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
Date: Fri, 10 Jul 2026 15:57:41 +0530
Subject: [PATCH 4/7] [CodeGen] Mark overload-dependent intrinsic feature
 checks

Keep generated feature checks on the intrinsic-ID cache and use the typed target hook only for intrinsics explicitly marked in TableGen. Mark amdgcn.ballot and prevent unsupported GlobalISel intrinsics from reaching instruction selection after diagnosis.

Co-authored-by: GPT-5 Codex <noreply at openai.com>
Signed-off-by: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
---
 .../llvm/CodeGen/GlobalISel/IRTranslator.h    |  3 ++
 .../llvm/CodeGen/TargetSubtargetInfo.h        |  9 ++--
 llvm/include/llvm/IR/Intrinsics.h             |  4 ++
 llvm/include/llvm/IR/Intrinsics.td            |  7 ++-
 llvm/include/llvm/IR/IntrinsicsAMDGPU.td      |  4 +-
 llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp  | 47 ++++++++++---------
 llvm/lib/CodeGen/TargetSubtargetInfo.cpp      | 15 ++++--
 llvm/lib/IR/DiagnosticInfo.cpp                |  3 ++
 llvm/lib/Target/AMDGPU/GCNSubtarget.cpp       |  8 ++--
 .../GlobalISel/llvm.amdgcn.ballot.i32.ll      |  2 +-
 .../TableGen/intrinsic-target-features.td     |  5 ++
 11 files changed, 73 insertions(+), 34 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/GlobalISel/IRTranslator.h b/llvm/include/llvm/CodeGen/GlobalISel/IRTranslator.h
index 9007d5276765f..afc29f1529eb7 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/IRTranslator.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/IRTranslator.h
@@ -297,6 +297,9 @@ class LLVM_ABI IRTranslator : public MachineFunctionPass {
   /// \pre \p U is a call instruction.
   bool translateCall(const User &U, MachineIRBuilder &MIRBuilder);
 
+  bool translateUnsupportedIntrinsic(const CallBase &CB, Intrinsic::ID ID,
+                                     MachineIRBuilder &MIRBuilder);
+
   bool translateIntrinsic(
       const CallBase &CB, Intrinsic::ID ID, MachineIRBuilder &MIRBuilder,
       ArrayRef<TargetLowering::IntrinsicInfo> TgtMemIntrinsicInfos = {});
diff --git a/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h b/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
index fd92130bbe0d9..29b531521dc43 100644
--- a/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
+++ b/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
@@ -95,14 +95,15 @@ class LLVM_ABI TargetSubtargetInfo : public MCSubtargetInfo {
   /// \returns true if the target intrinsic \p IntrinsicID is supported by this
   /// subtarget.
   bool isIntrinsicSupported(unsigned IntrinsicID) const;
+
+  /// Like the overload above, but uses \p FTy to resolve the feature expression
+  /// for intrinsics marked as requiring custom target features.
   bool isIntrinsicSupported(unsigned IntrinsicID,
                             const FunctionType *FTy) const;
 
   /// \returns the target features required by the target intrinsic
-  /// \p IntrinsicID with signature \p FTy.
-  ///
-  /// The intrinsic TargetFeatures table is keyed by intrinsic ID. Targets can
-  /// override this when support also depends on the resolved overload type.
+  /// \p IntrinsicID with signature \p FTy. Targets override this for
+  /// intrinsics marked as requiring custom target features.
   virtual StringRef
   getRequiredTargetFeaturesForIntrinsic(unsigned IntrinsicID,
                                         const FunctionType *FTy) const;
diff --git a/llvm/include/llvm/IR/Intrinsics.h b/llvm/include/llvm/IR/Intrinsics.h
index a4799751832bf..1ff527f4c2518 100644
--- a/llvm/include/llvm/IR/Intrinsics.h
+++ b/llvm/include/llvm/IR/Intrinsics.h
@@ -66,6 +66,10 @@ LLVM_ABI StringRef getBaseName(ID id);
 /// \returns the target feature expression required by an intrinsic.
 LLVM_ABI StringRef getRequiredTargetFeatures(ID id);
 
+/// Sentinel used when an intrinsic's required target features depend on its
+/// resolved overload type and must be provided by the target.
+inline constexpr StringLiteral CustomTargetFeatures = "$custom";
+
 /// Return the LLVM name for an intrinsic, such as "llvm.ppc.altivec.lvx" or
 /// "llvm.ssa.copy.p0s_s.1". Note, this version of getName supports overloads.
 /// This is less efficient than the StringRef version of this function.  If no
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index 37c9c783465d6..d679187cd5f3d 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -789,7 +789,8 @@ class TypeInfoGen<list<LLVMType> RetTypes, list<LLVMType> ParamTypes> {
 //    The empty string means no target features are required. The expression
 //    uses feature names from the target's subtarget feature table. Comma means
 //    AND, | means OR, comma has higher precedence than |, and parentheses group
-//    expressions.
+//    expressions. The special value "$custom" means the expression depends on
+//    the resolved overload type and is provided by the target.
 //
 class Intrinsic<list<LLVMType> ret_types,
                 list<LLVMType> param_types = [],
@@ -842,6 +843,10 @@ class RequiresTargetFeatures<string features> {
   string TargetFeatures = features;
 }
 
+/// RequiresCustomTargetFeatures - The required target features depend on the
+/// intrinsic's resolved overload type and are provided by the target.
+class RequiresCustomTargetFeatures : RequiresTargetFeatures<"$custom">;
+
 /// Utility class for intrinsics that
 /// 1. Don't touch memory or any hidden state
 /// 2. Can be freely speculated, and
diff --git a/llvm/include/llvm/IR/IntrinsicsAMDGPU.td b/llvm/include/llvm/IR/IntrinsicsAMDGPU.td
index 565637b36131c..49f600b47e897 100644
--- a/llvm/include/llvm/IR/IntrinsicsAMDGPU.td
+++ b/llvm/include/llvm/IR/IntrinsicsAMDGPU.td
@@ -2468,7 +2468,9 @@ def int_amdgcn_fcmp :
 // in all active lanes, and zero in all inactive lanes.
 def int_amdgcn_ballot :
   Intrinsic<[llvm_anyint_ty], [llvm_i1_ty],
-            [IntrNoMem, IntrConvergent, IntrWillReturn, IntrNoCallback, IntrNoFree, IntrNoCreateUndefOrPoison]>;
+            [IntrNoMem, IntrConvergent, IntrWillReturn, IntrNoCallback,
+             IntrNoFree, IntrNoCreateUndefOrPoison]>,
+  RequiresCustomTargetFeatures;
 
 // Inverse of ballot: return the bit corresponding to the current lane from the
 // given mask.
diff --git a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
index f422497aa27a9..175b6c9b1641b 100644
--- a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
@@ -2878,15 +2878,8 @@ bool IRTranslator::translateCall(const User &U, MachineIRBuilder &MIRBuilder) {
 
   assert(ID != Intrinsic::not_intrinsic && "unknown intrinsic");
 
-  if (!MF->getSubtarget().isIntrinsicSupported(ID, CI.getFunctionType())) {
-    const Function &Fn = MF->getFunction();
-    StringRef RequiredFeatures =
-        MF->getSubtarget().getRequiredTargetFeaturesForIntrinsic(
-            ID, CI.getFunctionType());
-    Fn.getContext().diagnose(DiagnosticInfoUnsupportedTargetIntrinsic(
-        Fn, ID, F->getName(), CI.getDebugLoc(), RequiredFeatures));
-    return false;
-  }
+  if (!MF->getSubtarget().isIntrinsicSupported(ID, CI.getFunctionType()))
+    return translateUnsupportedIntrinsic(CI, ID, MIRBuilder);
 
   if (translateKnownIntrinsic(CI, ID, MIRBuilder))
     return true;
@@ -2897,22 +2890,34 @@ bool IRTranslator::translateCall(const User &U, MachineIRBuilder &MIRBuilder) {
   return translateIntrinsic(CI, ID, MIRBuilder, Infos);
 }
 
+bool IRTranslator::translateUnsupportedIntrinsic(const CallBase &CB,
+                                                 Intrinsic::ID ID,
+                                                 MachineIRBuilder &MIRBuilder) {
+  const Function &F = MF->getFunction();
+  StringRef RequiredFeatures =
+      MF->getSubtarget().getRequiredTargetFeaturesForIntrinsic(
+          ID, CB.getFunctionType());
+  const Function *IntrinsicFn = CB.getCalledFunction();
+  StringRef IntrinsicName =
+      IntrinsicFn ? IntrinsicFn->getName() : Intrinsic::getBaseName(ID);
+  F.getContext().diagnose(DiagnosticInfoUnsupportedTargetIntrinsic(
+      F, ID, IntrinsicName, CB.getDebugLoc(), RequiredFeatures));
+
+  // The diagnostic makes compilation fail. Define any results so GlobalISel
+  // can finish without sending the unsupported intrinsic to instruction
+  // selection.
+  if (!CB.getType()->isVoidTy())
+    for (Register ResultReg : getOrCreateVRegs(CB))
+      MIRBuilder.buildUndef(ResultReg);
+  return true;
+}
+
 /// Translate a call or callbr to an intrinsic.
 bool IRTranslator::translateIntrinsic(
     const CallBase &CB, Intrinsic::ID ID, MachineIRBuilder &MIRBuilder,
     ArrayRef<TargetLowering::IntrinsicInfo> TgtMemIntrinsicInfos) {
-  if (!MF->getSubtarget().isIntrinsicSupported(ID, CB.getFunctionType())) {
-    const Function &F = MF->getFunction();
-    StringRef RequiredFeatures =
-        MF->getSubtarget().getRequiredTargetFeaturesForIntrinsic(
-            ID, CB.getFunctionType());
-    const Function *IntrinsicFn = CB.getCalledFunction();
-    StringRef IntrinsicName =
-        IntrinsicFn ? IntrinsicFn->getName() : Intrinsic::getBaseName(ID);
-    F.getContext().diagnose(DiagnosticInfoUnsupportedTargetIntrinsic(
-        F, ID, IntrinsicName, CB.getDebugLoc(), RequiredFeatures));
-    return false;
-  }
+  if (!MF->getSubtarget().isIntrinsicSupported(ID, CB.getFunctionType()))
+    return translateUnsupportedIntrinsic(CB, ID, MIRBuilder);
 
   ArrayRef<Register> ResultRegs;
   if (!CB.getType()->isVoidTy())
diff --git a/llvm/lib/CodeGen/TargetSubtargetInfo.cpp b/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
index 139f3dd1f908f..675ba692e3f0c 100644
--- a/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
+++ b/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
@@ -34,6 +34,9 @@ bool TargetSubtargetInfo::isIntrinsicSupported(unsigned IntrinsicID) const {
   if (RequiredFeatures.empty())
     return true;
 
+  if (RequiredFeatures == Intrinsic::CustomTargetFeatures)
+    return false;
+
   auto [It, Inserted] = IntrinsicSupportCache.try_emplace(IntrinsicID);
   if (Inserted)
     It->second = checkFeatureExpression(RequiredFeatures);
@@ -42,9 +45,15 @@ bool TargetSubtargetInfo::isIntrinsicSupported(unsigned IntrinsicID) const {
 
 bool TargetSubtargetInfo::isIntrinsicSupported(unsigned IntrinsicID,
                                                const FunctionType *FTy) const {
-  StringRef RequiredFeatures =
-      getRequiredTargetFeaturesForIntrinsic(IntrinsicID, FTy);
-  return RequiredFeatures.empty() || checkFeatureExpression(RequiredFeatures);
+  StringRef RequiredFeatures = Intrinsic::getRequiredTargetFeatures(
+      static_cast<Intrinsic::ID>(IntrinsicID));
+  if (RequiredFeatures != Intrinsic::CustomTargetFeatures)
+    return isIntrinsicSupported(IntrinsicID);
+
+  RequiredFeatures = getRequiredTargetFeaturesForIntrinsic(IntrinsicID, FTy);
+  return RequiredFeatures.empty() ||
+         (RequiredFeatures != Intrinsic::CustomTargetFeatures &&
+          checkFeatureExpression(RequiredFeatures));
 }
 
 StringRef TargetSubtargetInfo::getRequiredTargetFeaturesForIntrinsic(
diff --git a/llvm/lib/IR/DiagnosticInfo.cpp b/llvm/lib/IR/DiagnosticInfo.cpp
index 569e0e0c1374e..6d9a2a5f4e9f4 100644
--- a/llvm/lib/IR/DiagnosticInfo.cpp
+++ b/llvm/lib/IR/DiagnosticInfo.cpp
@@ -435,6 +435,9 @@ DiagnosticInfoUnsupportedTargetIntrinsic::
 }
 
 std::string DiagnosticInfoUnsupportedTargetIntrinsic::getMessage() const {
+  if (RequiredFeatures == Intrinsic::CustomTargetFeatures)
+    return (Twine(IntrinsicName) + " is not supported on this target").str();
+
   return (Twine(IntrinsicName) + " requires target feature '" +
           RequiredFeatures + "'")
       .str();
diff --git a/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp b/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp
index 6e8e1bfc34234..bce29bb4e2dda 100644
--- a/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp
+++ b/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp
@@ -59,9 +59,11 @@ GCNSubtarget::~GCNSubtarget() = default;
 
 StringRef GCNSubtarget::getRequiredTargetFeaturesForIntrinsic(
     unsigned IntrinsicID, const FunctionType *FTy) const {
-  if (FTy && IntrinsicID == Intrinsic::amdgcn_ballot &&
-      FTy->getReturnType()->isIntegerTy(32))
-    return "wavefrontsize32";
+  if (IntrinsicID == Intrinsic::amdgcn_ballot) {
+    if (FTy && FTy->getReturnType()->isIntegerTy(32))
+      return "wavefrontsize32";
+    return {};
+  }
 
   return TargetSubtargetInfo::getRequiredTargetFeaturesForIntrinsic(IntrinsicID,
                                                                     FTy);
diff --git a/llvm/test/CodeGen/AMDGPU/GlobalISel/llvm.amdgcn.ballot.i32.ll b/llvm/test/CodeGen/AMDGPU/GlobalISel/llvm.amdgcn.ballot.i32.ll
index af1a2eab576e5..bb838f34a7334 100644
--- a/llvm/test/CodeGen/AMDGPU/GlobalISel/llvm.amdgcn.ballot.i32.ll
+++ b/llvm/test/CodeGen/AMDGPU/GlobalISel/llvm.amdgcn.ballot.i32.ll
@@ -2,7 +2,7 @@
 ; RUN: llc -mtriple=amdgpu10.10 -global-isel < %s | FileCheck -check-prefixes=CHECK,GFX10 %s
 ; RUN: llc -mtriple=amdgpu11.00 -mattr=+real-true16 -amdgpu-enable-delay-alu=0 -global-isel < %s | FileCheck -check-prefixes=CHECK,GFX11,GFX11-TRUE16 %s
 ; RUN: llc -mtriple=amdgpu11.00 -mattr=-real-true16 -amdgpu-enable-delay-alu=0 -global-isel < %s | FileCheck -check-prefixes=CHECK,GFX11,GFX11-FAKE16 %s
-; RUN: llvm-extract --func=constant_false -S %s | not llc -mtriple=amdgpu9.00 -global-isel -global-isel-abort=0 -filetype=null 2>&1 | FileCheck -check-prefix=ERR %s
+; RUN: llvm-extract --func=constant_false -S %s | not llc -mtriple=amdgpu9.00 -global-isel -filetype=null 2>&1 | FileCheck -check-prefix=ERR %s
 
 ; ERR: error: {{.*}}: in function {{@?}}constant_false{{.*}}: llvm.amdgcn.ballot.i32 requires target feature 'wavefrontsize32'
 
diff --git a/llvm/test/TableGen/intrinsic-target-features.td b/llvm/test/TableGen/intrinsic-target-features.td
index e43985cab1d06..c294bce53e102 100644
--- a/llvm/test/TableGen/intrinsic-target-features.td
+++ b/llvm/test/TableGen/intrinsic-target-features.td
@@ -4,6 +4,9 @@ include "llvm/IR/Intrinsics.td"
 
 def int_no_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>;
 
+def int_custom_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>,
+  RequiresCustomTargetFeatures;
+
 def int_requires_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>,
   RequiresTargetFeatures<"feat-a,(feat-b|feat-c)">;
 
@@ -15,12 +18,14 @@ def int_let_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>;
 // CHECK:      // Intrinsic ID to required target features table.
 // CHECK:      static constexpr char IntrinsicTargetFeaturesTableStorage[] =
 // CHECK-NEXT: "\0"
+// CHECK-NEXT: "$custom\0"
 // CHECK-NEXT: "feat-let-a,feat-let-b\0"
 // CHECK-NEXT: "feat-a,(feat-b|feat-c)\0"
 // CHECK:      static constexpr llvm::StringTable
 // CHECK-NEXT: IntrinsicTargetFeaturesTable = IntrinsicTargetFeaturesTableStorage;
 // CHECK:      static constexpr unsigned IntrinsicTargetFeaturesOffsetTable[] = {
 // CHECK-NEXT:   0, // not_intrinsic
+// CHECK-NEXT:   {{[0-9]+}}, // llvm.custom.feature
 // CHECK-NEXT:   {{[0-9]+}}, // llvm.let.feature
 // CHECK-NEXT:   0, // llvm.no.feature
 // CHECK-NEXT:   {{[0-9]+}}, // llvm.requires.feature

>From b050a23a260dbe2468495b0e1c6e755b35e8a54b Mon Sep 17 00:00:00 2001
From: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
Date: Wed, 22 Jul 2026 18:03:57 +0530
Subject: [PATCH 5/7] [CodeGen] Retain intrinsic signatures in unsupported
 diagnostics

Keep the intrinsic ID and FunctionType in DiagnosticInfoUnsupportedTargetIntrinsic instead of storing a preformatted declaration name. This preserves enough information for frontends and reconstructs the canonical overloaded name only when rendering the diagnostic.

Use an optional feature expression to distinguish an unsupported overload from one with a concrete requirement. Keep the TableGen custom marker confined to target feature dispatch and retain the intrinsic-ID cache for static expressions.

Document the new states and update affected checks to expect exact overloaded intrinsic names.

Co-authored-by: GPT-5 Codex <noreply at openai.com>
Signed-off-by: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
---
 .../llvm/CodeGen/TargetSubtargetInfo.h        | 11 ++++---
 llvm/include/llvm/IR/DiagnosticInfo.h         | 22 ++++++++------
 llvm/include/llvm/IR/Intrinsics.h             |  4 ++-
 llvm/include/llvm/IR/Intrinsics.td            |  3 +-
 llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp  |  7 ++---
 .../SelectionDAG/SelectionDAGBuilder.cpp      | 14 ++++-----
 llvm/lib/CodeGen/TargetSubtargetInfo.cpp      | 17 +++++++----
 llvm/lib/IR/DiagnosticInfo.cpp                | 29 ++++++++++++-------
 llvm/lib/Target/AMDGPU/GCNSubtarget.cpp       |  8 +++--
 llvm/lib/Target/AMDGPU/GCNSubtarget.h         |  3 +-
 .../CodeGen/AMDGPU/llvm.amdgcn.exp.compr.ll   |  2 +-
 .../CodeGen/AMDGPU/llvm.amdgcn.mov.dpp8.ll    |  2 +-
 .../CodeGen/AMDGPU/llvm.amdgcn.permlane.ll    |  2 +-
 llvm/test/CodeGen/AMDGPU/llvm.amdgcn.tanh.ll  |  2 +-
 .../CodeGen/AMDGPU/unsupported-av-load.ll     |  2 +-
 .../CodeGen/AMDGPU/unsupported-av-store.ll    |  2 +-
 16 files changed, 76 insertions(+), 54 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h b/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
index 29b531521dc43..c5c7b09542c92 100644
--- a/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
+++ b/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
@@ -26,6 +26,7 @@
 #include "llvm/Support/CodeGen.h"
 #include "llvm/Support/Compiler.h"
 #include <memory>
+#include <optional>
 #include <vector>
 
 namespace llvm {
@@ -101,10 +102,12 @@ class LLVM_ABI TargetSubtargetInfo : public MCSubtargetInfo {
   bool isIntrinsicSupported(unsigned IntrinsicID,
                             const FunctionType *FTy) const;
 
-  /// \returns the target features required by the target intrinsic
-  /// \p IntrinsicID with signature \p FTy. Targets override this for
-  /// intrinsics marked as requiring custom target features.
-  virtual StringRef
+  /// Returns the target features required by the target intrinsic
+  /// \p IntrinsicID with signature \p FTy. An empty expression means no
+  /// features are required; \c std::nullopt means no feature expression
+  /// supports the intrinsic. Targets override this for intrinsics marked as
+  /// requiring custom target features.
+  virtual std::optional<StringRef>
   getRequiredTargetFeaturesForIntrinsic(unsigned IntrinsicID,
                                         const FunctionType *FTy) const;
 
diff --git a/llvm/include/llvm/IR/DiagnosticInfo.h b/llvm/include/llvm/IR/DiagnosticInfo.h
index 4067d267df5b9..f2c52ffccab00 100644
--- a/llvm/include/llvm/IR/DiagnosticInfo.h
+++ b/llvm/include/llvm/IR/DiagnosticInfo.h
@@ -40,6 +40,7 @@ class DIFile;
 class DISubprogram;
 class CallInst;
 class Function;
+class FunctionType;
 class Instruction;
 class InstructionCost;
 class Module;
@@ -1133,23 +1134,26 @@ class LLVM_ABI DiagnosticInfoUnsupportedTargetIntrinsic
     : public DiagnosticInfoWithLocationBase {
 private:
   unsigned IntrinsicID;
-  std::string IntrinsicName;
-  StringRef RequiredFeatures;
+  FunctionType *IntrinsicType;
+  std::optional<StringRef> RequiredFeatures;
 
 public:
-  DiagnosticInfoUnsupportedTargetIntrinsic(const Function &Fn,
-                                           unsigned IntrinsicID,
-                                           StringRef IntrinsicName,
-                                           const DiagnosticLocation &Loc,
-                                           StringRef RequiredFeatures);
+  DiagnosticInfoUnsupportedTargetIntrinsic(
+      const Function &Fn, unsigned IntrinsicID, FunctionType *IntrinsicType,
+      const DiagnosticLocation &Loc, std::optional<StringRef> RequiredFeatures);
 
   static bool classof(const DiagnosticInfo *DI) {
     return DI->getKind() == DK_UnsupportedTargetIntrinsic;
   }
 
   unsigned getIntrinsicID() const { return IntrinsicID; }
-  StringRef getIntrinsicName() const { return IntrinsicName; }
-  StringRef getRequiredFeatures() const { return RequiredFeatures; }
+  FunctionType *getIntrinsicType() const { return IntrinsicType; }
+
+  /// Returns the required feature expression, or \c std::nullopt if no feature
+  /// expression supports this intrinsic overload.
+  std::optional<StringRef> getRequiredFeatures() const {
+    return RequiredFeatures;
+  }
   std::string getMessage() const;
 
   void print(DiagnosticPrinter &DP) const override;
diff --git a/llvm/include/llvm/IR/Intrinsics.h b/llvm/include/llvm/IR/Intrinsics.h
index 1ff527f4c2518..552cc37f5f317 100644
--- a/llvm/include/llvm/IR/Intrinsics.h
+++ b/llvm/include/llvm/IR/Intrinsics.h
@@ -63,7 +63,9 @@ LLVM_ABI StringRef getName(ID id);
 /// overloading, such as "llvm.ssa.copy".
 LLVM_ABI StringRef getBaseName(ID id);
 
-/// \returns the target feature expression required by an intrinsic.
+/// \returns the static target feature expression required by an intrinsic, or
+/// \c CustomTargetFeatures when it must be resolved from the overload type by
+/// the target.
 LLVM_ABI StringRef getRequiredTargetFeatures(ID id);
 
 /// Sentinel used when an intrinsic's required target features depend on its
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index d679187cd5f3d..4f56eef1968b9 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -844,7 +844,8 @@ class RequiresTargetFeatures<string features> {
 }
 
 /// RequiresCustomTargetFeatures - The required target features depend on the
-/// intrinsic's resolved overload type and are provided by the target.
+/// intrinsic's resolved overload type and are provided by the target. The
+/// target may reject an overload when no feature expression supports it.
 class RequiresCustomTargetFeatures : RequiresTargetFeatures<"$custom">;
 
 /// Utility class for intrinsics that
diff --git a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
index 175b6c9b1641b..6d769fbc57036 100644
--- a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
@@ -2894,14 +2894,11 @@ bool IRTranslator::translateUnsupportedIntrinsic(const CallBase &CB,
                                                  Intrinsic::ID ID,
                                                  MachineIRBuilder &MIRBuilder) {
   const Function &F = MF->getFunction();
-  StringRef RequiredFeatures =
+  std::optional<StringRef> RequiredFeatures =
       MF->getSubtarget().getRequiredTargetFeaturesForIntrinsic(
           ID, CB.getFunctionType());
-  const Function *IntrinsicFn = CB.getCalledFunction();
-  StringRef IntrinsicName =
-      IntrinsicFn ? IntrinsicFn->getName() : Intrinsic::getBaseName(ID);
   F.getContext().diagnose(DiagnosticInfoUnsupportedTargetIntrinsic(
-      F, ID, IntrinsicName, CB.getDebugLoc(), RequiredFeatures));
+      F, ID, CB.getFunctionType(), CB.getDebugLoc(), RequiredFeatures));
 
   // The diagnostic makes compilation fail. Define any results so GlobalISel
   // can finish without sending the unsupported intrinsic to instruction
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index 2a40a7c8403b5..72c0a029cca0f 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -5567,15 +5567,13 @@ void SelectionDAGBuilder::visitTargetIntrinsic(const CallInst &I,
   if (!DAG.getMachineFunction().getSubtarget().isIntrinsicSupported(
           Intrinsic, I.getFunctionType())) {
     SDLoc DL = getCurSDLoc();
-    StringRef RequiredFeatures = DAG.getMachineFunction()
-                                     .getSubtarget()
-                                     .getRequiredTargetFeaturesForIntrinsic(
-                                         Intrinsic, I.getFunctionType());
-    const Function *IntrinsicFn = I.getCalledFunction();
-    StringRef IntrinsicName = IntrinsicFn ? IntrinsicFn->getName()
-                                          : Intrinsic::getBaseName(IntrinsicID);
+    std::optional<StringRef> RequiredFeatures =
+        DAG.getMachineFunction()
+            .getSubtarget()
+            .getRequiredTargetFeaturesForIntrinsic(Intrinsic,
+                                                   I.getFunctionType());
     DAG.getContext()->diagnose(DiagnosticInfoUnsupportedTargetIntrinsic(
-        *I.getFunction(), IntrinsicID, IntrinsicName, DL.getDebugLoc(),
+        *I.getFunction(), IntrinsicID, I.getFunctionType(), DL.getDebugLoc(),
         RequiredFeatures));
 
     // The intrinsic is not available on this subtarget. Preserve the chain for
diff --git a/llvm/lib/CodeGen/TargetSubtargetInfo.cpp b/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
index 675ba692e3f0c..30891c41e181e 100644
--- a/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
+++ b/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
@@ -50,16 +50,21 @@ bool TargetSubtargetInfo::isIntrinsicSupported(unsigned IntrinsicID,
   if (RequiredFeatures != Intrinsic::CustomTargetFeatures)
     return isIntrinsicSupported(IntrinsicID);
 
-  RequiredFeatures = getRequiredTargetFeaturesForIntrinsic(IntrinsicID, FTy);
-  return RequiredFeatures.empty() ||
-         (RequiredFeatures != Intrinsic::CustomTargetFeatures &&
-          checkFeatureExpression(RequiredFeatures));
+  std::optional<StringRef> CustomRequiredFeatures =
+      getRequiredTargetFeaturesForIntrinsic(IntrinsicID, FTy);
+  return CustomRequiredFeatures &&
+         (CustomRequiredFeatures->empty() ||
+          checkFeatureExpression(*CustomRequiredFeatures));
 }
 
-StringRef TargetSubtargetInfo::getRequiredTargetFeaturesForIntrinsic(
+std::optional<StringRef>
+TargetSubtargetInfo::getRequiredTargetFeaturesForIntrinsic(
     unsigned IntrinsicID, const FunctionType *FTy) const {
-  return Intrinsic::getRequiredTargetFeatures(
+  StringRef RequiredFeatures = Intrinsic::getRequiredTargetFeatures(
       static_cast<Intrinsic::ID>(IntrinsicID));
+  if (RequiredFeatures == Intrinsic::CustomTargetFeatures)
+    return std::nullopt;
+  return RequiredFeatures;
 }
 
 bool TargetSubtargetInfo::enableAtomicExpand() const {
diff --git a/llvm/lib/IR/DiagnosticInfo.cpp b/llvm/lib/IR/DiagnosticInfo.cpp
index 6d9a2a5f4e9f4..a4c751e8a979a 100644
--- a/llvm/lib/IR/DiagnosticInfo.cpp
+++ b/llvm/lib/IR/DiagnosticInfo.cpp
@@ -420,26 +420,35 @@ void DiagnosticInfoUnsupported::print(DiagnosticPrinter &DP) const {
 }
 
 DiagnosticInfoUnsupportedTargetIntrinsic::
-    DiagnosticInfoUnsupportedTargetIntrinsic(const Function &Fn,
-                                             unsigned IntrinsicID,
-                                             StringRef IntrinsicName,
-                                             const DiagnosticLocation &Loc,
-                                             StringRef RequiredFeatures)
+    DiagnosticInfoUnsupportedTargetIntrinsic(
+        const Function &Fn, unsigned IntrinsicID, FunctionType *IntrinsicType,
+        const DiagnosticLocation &Loc,
+        std::optional<StringRef> RequiredFeatures)
     : DiagnosticInfoWithLocationBase(DK_UnsupportedTargetIntrinsic, DS_Error,
                                      Fn, Loc),
-      IntrinsicID(IntrinsicID), IntrinsicName(IntrinsicName),
+      IntrinsicID(IntrinsicID), IntrinsicType(IntrinsicType),
       RequiredFeatures(RequiredFeatures) {
-  assert(!RequiredFeatures.empty() &&
+  assert(IntrinsicType && "intrinsic type should not be null");
+  assert((!RequiredFeatures || !RequiredFeatures->empty()) &&
          "intrinsic without required features should be supported");
-  assert(!IntrinsicName.empty() && "intrinsic name should not be empty");
 }
 
 std::string DiagnosticInfoUnsupportedTargetIntrinsic::getMessage() const {
-  if (RequiredFeatures == Intrinsic::CustomTargetFeatures)
+  Intrinsic::ID ID = static_cast<Intrinsic::ID>(IntrinsicID);
+  SmallVector<Type *, 4> OverloadTys;
+  [[maybe_unused]] bool IsValid =
+      Intrinsic::isSignatureValid(ID, IntrinsicType, OverloadTys);
+  assert(IsValid && "invalid intrinsic type");
+  // Name uniquing for overloads involving unnamed types updates module state.
+  Module *M = const_cast<Module *>(getFunction().getParent());
+  std::string IntrinsicName =
+      Intrinsic::getName(ID, OverloadTys, M, IntrinsicType);
+
+  if (!RequiredFeatures)
     return (Twine(IntrinsicName) + " is not supported on this target").str();
 
   return (Twine(IntrinsicName) + " requires target feature '" +
-          RequiredFeatures + "'")
+          *RequiredFeatures + "'")
       .str();
 }
 
diff --git a/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp b/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp
index bce29bb4e2dda..a62153cee5888 100644
--- a/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp
+++ b/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp
@@ -57,12 +57,14 @@ static cl::opt<unsigned>
 
 GCNSubtarget::~GCNSubtarget() = default;
 
-StringRef GCNSubtarget::getRequiredTargetFeaturesForIntrinsic(
+std::optional<StringRef> GCNSubtarget::getRequiredTargetFeaturesForIntrinsic(
     unsigned IntrinsicID, const FunctionType *FTy) const {
   if (IntrinsicID == Intrinsic::amdgcn_ballot) {
-    if (FTy && FTy->getReturnType()->isIntegerTy(32))
+    if (!FTy)
+      return std::nullopt;
+    if (FTy->getReturnType()->isIntegerTy(32))
       return "wavefrontsize32";
-    return {};
+    return StringRef();
   }
 
   return TargetSubtargetInfo::getRequiredTargetFeaturesForIntrinsic(IntrinsicID,
diff --git a/llvm/lib/Target/AMDGPU/GCNSubtarget.h b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
index 12dd3f6b31f93..ca7e6e22ab24d 100644
--- a/llvm/lib/Target/AMDGPU/GCNSubtarget.h
+++ b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
@@ -23,6 +23,7 @@
 #include "Utils/AMDGPUBaseInfo.h"
 #include "llvm/Support/AMDHSAKernelDescriptor.h"
 #include "llvm/Support/ErrorHandling.h"
+#include <optional>
 
 #define GET_SUBTARGETINFO_HEADER
 #include "AMDGPUGenSubtargetInfo.inc"
@@ -125,7 +126,7 @@ class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
   /// function \p F.
   void checkSubtargetFeatures(const Function &F) const;
 
-  StringRef
+  std::optional<StringRef>
   getRequiredTargetFeaturesForIntrinsic(unsigned IntrinsicID,
                                         const FunctionType *FTy) const override;
 
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.exp.compr.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.exp.compr.ll
index 395b3dec7e2d2..e5501bcc38f5b 100644
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.exp.compr.ll
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.exp.compr.ll
@@ -5,7 +5,7 @@
 ; RUN: not llc -global-isel=0 -mtriple=amdgpu11.00 < %s 2>&1 | FileCheck -strict-whitespace -check-prefix=ERR %s
 ; RUN: not llc -global-isel=1 -mtriple=amdgpu11.00 < %s 2>&1 | FileCheck -strict-whitespace -check-prefix=ERR %s
 
-; ERR: error: <unknown>:0:0: in function @test_export_compr_zeroes_v2f16 void (): llvm.amdgcn.exp.compr requires target feature '-gfx11-insts'
+; ERR: error: <unknown>:0:0: in function @test_export_compr_zeroes_v2f16 void (): llvm.amdgcn.exp.compr.v2f16 requires target feature '-gfx11-insts'
 
 declare void @llvm.amdgcn.exp.compr.v2f16(i32, i32, <2 x half>, <2 x half>, i1, i1) #0
 declare void @llvm.amdgcn.exp.compr.v2i16(i32, i32, <2 x i16>, <2 x i16>, i1, i1) #0
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.mov.dpp8.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.mov.dpp8.ll
index e062a9239f7c3..4a454d278d170 100644
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.mov.dpp8.ll
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.mov.dpp8.ll
@@ -8,7 +8,7 @@
 ; RUN: not llc -global-isel=0 -mtriple=amdgpu9.00 -filetype=null < %s 2>&1 | FileCheck -check-prefix=ERR %s
 ; xUN: not llc -global-isel=1 -global-isel-abort=0 -mtriple=amdgpu9.00 -filetype=null < %s 2>&1 | FileCheck -check-prefix=ERR %s
 
-; ERR: error: <unknown>:0:0: in function @dpp8_test void (ptr addrspace(1), i32): llvm.amdgcn.mov.dpp8 requires target feature 'dpp8'
+; ERR: error: <unknown>:0:0: in function @dpp8_test void (ptr addrspace(1), i32): llvm.amdgcn.mov.dpp8.i32 requires target feature 'dpp8'
 
 ; GFX10PLUS-LABEL: {{^}}dpp8_test:
 ; GFX10PLUS: v_mov_b32_e32 [[SRC:v[0-9]+]], s{{[0-9]+}}
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.permlane.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.permlane.ll
index c757e17339d14..7ba40fa912f34 100644
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.permlane.ll
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.permlane.ll
@@ -11,7 +11,7 @@
 ; RUN: not llc -global-isel=0 -mtriple=amdgpu9.00 -filetype=null < %s 2>&1 | FileCheck -check-prefix=ERR %s
 ;xUN: not llc -global-isel=1 -global-isel-abort=0 -mtriple=amdgpu9.00 -filetype=null < %s 2>&1 | FileCheck -check-prefix=ERR %s
 
-; ERR: error: <unknown>:0:0: in function @v_permlane16_b32_vss_i32 void (ptr addrspace(1), i32, i32, i32): llvm.amdgcn.permlane16 requires target feature 'permlane16-insts'
+; ERR: error: <unknown>:0:0: in function @v_permlane16_b32_vss_i32 void (ptr addrspace(1), i32, i32, i32): llvm.amdgcn.permlane16.i32 requires target feature 'permlane16-insts'
 
 declare i32 @llvm.amdgcn.permlane16(i32, i32, i32, i32, i1, i1)
 declare i32 @llvm.amdgcn.permlanex16(i32, i32, i32, i32, i1, i1)
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.tanh.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.tanh.ll
index 976b6d385b9dd..067930e0f60db 100644
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.tanh.ll
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.tanh.ll
@@ -11,7 +11,7 @@
 ; RUN: not llc -global-isel=0 -mtriple=amdgpu9.50 -filetype=null < %s 2>&1 | FileCheck -check-prefix=ERR %s
 ; RUN: not llc -global-isel=1 -mtriple=amdgpu9.50 -filetype=null < %s 2>&1 | FileCheck -check-prefix=ERR %s
 
-; ERR: error: <unknown>:0:0: in function @tanh_f32 float (float): llvm.amdgcn.tanh requires target feature 'tanh-insts'
+; ERR: error: <unknown>:0:0: in function @tanh_f32 float (float): llvm.amdgcn.tanh.f32 requires target feature 'tanh-insts'
 
 define float @tanh_f32(float %src) {
 ; GFX1250-LABEL: tanh_f32:
diff --git a/llvm/test/CodeGen/AMDGPU/unsupported-av-load.ll b/llvm/test/CodeGen/AMDGPU/unsupported-av-load.ll
index 9a7a06853cfc6..59ba541d777e6 100644
--- a/llvm/test/CodeGen/AMDGPU/unsupported-av-load.ll
+++ b/llvm/test/CodeGen/AMDGPU/unsupported-av-load.ll
@@ -7,7 +7,7 @@
 ; RUN: not llc -global-isel=1 -mtriple=amdgpu8.10 -filetype=null < %s 2>&1 | FileCheck -check-prefix=ERR %s
 
 define <4 x i32> @av_load_b128(ptr addrspace(1) %addr) {
-; ERR: error: {{.*}}: in function @av_load_b128 {{.*}}: llvm.amdgcn.av.load.b128 requires target feature 'flat-global-insts'
+; ERR: error: {{.*}}: in function @av_load_b128 {{.*}}: llvm.amdgcn.av.load.b128.p1 requires target feature 'flat-global-insts'
 entry:
   %data = call <4 x i32> @llvm.amdgcn.av.load.b128.p1(ptr addrspace(1) %addr, metadata !0)
   ret <4 x i32> %data
diff --git a/llvm/test/CodeGen/AMDGPU/unsupported-av-store.ll b/llvm/test/CodeGen/AMDGPU/unsupported-av-store.ll
index c0810d4cc4546..ade06b5a99370 100644
--- a/llvm/test/CodeGen/AMDGPU/unsupported-av-store.ll
+++ b/llvm/test/CodeGen/AMDGPU/unsupported-av-store.ll
@@ -7,7 +7,7 @@
 ; RUN: not llc -global-isel=1 -mtriple=amdgpu8.10 -filetype=null < %s 2>&1 | FileCheck -check-prefix=ERR %s
 
 define void @av_store_b128(ptr addrspace(1) %addr, <4 x i32> %data) {
-; ERR: error: {{.*}}: in function @av_store_b128 {{.*}}: llvm.amdgcn.av.store.b128 requires target feature 'flat-global-insts'
+; ERR: error: {{.*}}: in function @av_store_b128 {{.*}}: llvm.amdgcn.av.store.b128.p1 requires target feature 'flat-global-insts'
 entry:
   call void @llvm.amdgcn.av.store.b128.p1(ptr addrspace(1) %addr, <4 x i32> %data, metadata !0)
   ret void

>From 89c3667c0808ce4bc004ac62ab47157bd564cfeb Mon Sep 17 00:00:00 2001
From: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
Date: Fri, 11 Sep 2026 11:13:51 +0530
Subject: [PATCH 6/7] [CodeGen] Keep intrinsic support checks on cached path

Query the intrinsic-ID support path first so statically supported intrinsics return through the existing cache. Resolve type-dependent feature requirements only after that check fails.

Co-authored-by: GPT-5 Codex <noreply at openai.com>
Signed-off-by: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
---
 llvm/lib/CodeGen/TargetSubtargetInfo.cpp | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/CodeGen/TargetSubtargetInfo.cpp b/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
index 30891c41e181e..9475ecab5e075 100644
--- a/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
+++ b/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
@@ -45,16 +45,13 @@ bool TargetSubtargetInfo::isIntrinsicSupported(unsigned IntrinsicID) const {
 
 bool TargetSubtargetInfo::isIntrinsicSupported(unsigned IntrinsicID,
                                                const FunctionType *FTy) const {
-  StringRef RequiredFeatures = Intrinsic::getRequiredTargetFeatures(
-      static_cast<Intrinsic::ID>(IntrinsicID));
-  if (RequiredFeatures != Intrinsic::CustomTargetFeatures)
-    return isIntrinsicSupported(IntrinsicID);
+  if (isIntrinsicSupported(IntrinsicID))
+    return true;
 
-  std::optional<StringRef> CustomRequiredFeatures =
+  std::optional<StringRef> RequiredFeatures =
       getRequiredTargetFeaturesForIntrinsic(IntrinsicID, FTy);
-  return CustomRequiredFeatures &&
-         (CustomRequiredFeatures->empty() ||
-          checkFeatureExpression(*CustomRequiredFeatures));
+  return RequiredFeatures && (RequiredFeatures->empty() ||
+                              checkFeatureExpression(*RequiredFeatures));
 }
 
 std::optional<StringRef>

>From 0b01b20c9f3dbafccbe6385237691ca79cf88373 Mon Sep 17 00:00:00 2001
From: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
Date: Tue, 22 Sep 2026 18:06:52 +0530
Subject: [PATCH 7/7] [CodeGen] Support mixed custom intrinsic feature checks

Allow a static target feature expression to precede the final $custom marker, while keeping overload-dependent validation in the target hook. Update AMDGPU ballot handling and affected diagnostics.

Co-authored-by: OpenAI Codex <noreply at openai.com>
Signed-off-by: Keshav Vinayak Jha <keshavvinayakjha at gmail.com>
---
 .../llvm/CodeGen/TargetSubtargetInfo.h        | 18 ++++++++-----
 llvm/include/llvm/IR/Intrinsics.h             | 11 ++++----
 llvm/include/llvm/IR/Intrinsics.td            | 16 +++++------
 llvm/include/llvm/IR/IntrinsicsAMDGPU.td      |  4 +--
 llvm/lib/CodeGen/TargetSubtargetInfo.cpp      | 27 ++++++++++++++-----
 llvm/lib/Target/AMDGPU/GCNSubtarget.cpp       |  7 ++---
 llvm/lib/Target/AMDGPU/GCNSubtarget.h         |  5 ++--
 .../llvm.amdgcn.wmma.target-features.ll       | 12 ++++-----
 .../TableGen/intrinsic-target-features.td     | 17 ++++++++++--
 .../TableGen/Basic/CodeGenIntrinsics.cpp      | 12 +++++++++
 10 files changed, 87 insertions(+), 42 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h b/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
index 9ed47ce3840e8..1ad8b5daf09f2 100644
--- a/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
+++ b/llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
@@ -105,15 +105,21 @@ class LLVM_ABI TargetSubtargetInfo : public MCSubtargetInfo {
   bool isIntrinsicSupported(unsigned IntrinsicID,
                             const FunctionType *FTy) const;
 
-  /// Returns the target features required by the target intrinsic
-  /// \p IntrinsicID with signature \p FTy. An empty expression means no
-  /// features are required; \c std::nullopt means no feature expression
-  /// supports the intrinsic. Targets override this for intrinsics marked as
-  /// requiring custom target features.
-  virtual std::optional<StringRef>
+  /// Returns the feature expression that makes target intrinsic \p IntrinsicID
+  /// with signature \p FTy unsupported. An empty expression means no features
+  /// are required; \c std::nullopt means no feature expression supports the
+  /// intrinsic.
+  std::optional<StringRef>
   getRequiredTargetFeaturesForIntrinsic(unsigned IntrinsicID,
                                         const FunctionType *FTy) const;
 
+  /// Returns the overload-dependent target features for an intrinsic whose
+  /// target feature expression contains \c $custom. Targets override this to
+  /// implement the custom part of the support check.
+  virtual std::optional<StringRef>
+  getCustomRequiredTargetFeaturesForIntrinsic(unsigned IntrinsicID,
+                                              const FunctionType *FTy) const;
+
   // Interfaces to the major aspects of target machine information:
   //
   // -- Instruction opcode and operand information
diff --git a/llvm/include/llvm/IR/Intrinsics.h b/llvm/include/llvm/IR/Intrinsics.h
index 2c34abac7ef7c..5e487bd2d76af 100644
--- a/llvm/include/llvm/IR/Intrinsics.h
+++ b/llvm/include/llvm/IR/Intrinsics.h
@@ -64,13 +64,14 @@ LLVM_ABI StringRef getName(ID id);
 /// overloading, such as "llvm.ssa.copy".
 LLVM_ABI StringRef getBaseName(ID id);
 
-/// \returns the static target feature expression required by an intrinsic, or
-/// \c CustomTargetFeatures when it must be resolved from the overload type by
-/// the target.
+/// \returns the target feature expression required by an intrinsic. A final
+/// \c CustomTargetFeatures term indicates that an additional check must be
+/// resolved from the overload type by the target.
 LLVM_ABI StringRef getRequiredTargetFeatures(ID id);
 
-/// Sentinel used when an intrinsic's required target features depend on its
-/// resolved overload type and must be provided by the target.
+/// Sentinel used as an entire target feature expression or its final
+/// comma-separated term when support requires an overload-dependent target
+/// check.
 inline constexpr StringLiteral CustomTargetFeatures = "$custom";
 
 /// Return the LLVM name for an intrinsic, such as "llvm.ppc.altivec.lvx" or
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index c0fc848691972..607f43143552e 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -811,8 +811,10 @@ class TypeInfoGen<list<LLVMType> RetTypes, list<LLVMType> ParamTypes> {
 //    The empty string means no target features are required. The expression
 //    uses feature names from the target's subtarget feature table. Comma means
 //    AND, | means OR, comma has higher precedence than |, and parentheses group
-//    expressions. The special value "$custom" means the expression depends on
-//    the resolved overload type and is provided by the target.
+//    expressions. The special "$custom" marker may be used as the entire
+//    expression or its final comma-separated term. In the latter form, the
+//    preceding expression is a static requirement and the target provides an
+//    additional overload-dependent requirement.
 //
 class Intrinsic<list<LLVMType> ret_types,
                 list<LLVMType> param_types = [],
@@ -860,16 +862,14 @@ class MSBuiltin<string name> {
 /// this specifies the required feature expression using feature names from the
 /// target's subtarget feature table. The expression grammar matches Clang
 /// builtins: comma means AND, | means OR, comma has higher precedence than |,
-/// and parentheses group expressions.
+/// and parentheses group expressions. The special "$custom" marker may be used
+/// as the entire expression or its final comma-separated term. In the latter
+/// form, the preceding expression is a static requirement and the target
+/// provides an additional overload-dependent requirement.
 class RequiresTargetFeatures<string features> {
   string TargetFeatures = features;
 }
 
-/// RequiresCustomTargetFeatures - The required target features depend on the
-/// intrinsic's resolved overload type and are provided by the target. The
-/// target may reject an overload when no feature expression supports it.
-class RequiresCustomTargetFeatures : RequiresTargetFeatures<"$custom">;
-
 /// Utility class for intrinsics that
 /// 1. Don't touch memory or any hidden state
 /// 2. Can be freely speculated, and
diff --git a/llvm/include/llvm/IR/IntrinsicsAMDGPU.td b/llvm/include/llvm/IR/IntrinsicsAMDGPU.td
index d01ec9ff05fe0..3698b19014e22 100644
--- a/llvm/include/llvm/IR/IntrinsicsAMDGPU.td
+++ b/llvm/include/llvm/IR/IntrinsicsAMDGPU.td
@@ -2515,11 +2515,11 @@ def int_amdgcn_cvt_pk_u8_f32 :
 
 // Returns a bitfield(i32 or i64) containing the result of its i1 argument
 // in all active lanes, and zero in all inactive lanes.
+let TargetFeatures = "$custom" in
 def int_amdgcn_ballot :
   Intrinsic<[llvm_anyint_ty], [llvm_i1_ty],
             [IntrNoMem, IntrConvergent, IntrWillReturn, IntrNoCallback,
-             IntrNoFree, IntrNoCreateUndefOrPoison]>,
-  RequiresCustomTargetFeatures;
+             IntrNoFree, IntrNoCreateUndefOrPoison]>;
 
 // Inverse of ballot: return the bit corresponding to the current lane from the
 // given mask.
diff --git a/llvm/lib/CodeGen/TargetSubtargetInfo.cpp b/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
index 7408c9bb12294..d1fe5d82f0ba2 100644
--- a/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
+++ b/llvm/lib/CodeGen/TargetSubtargetInfo.cpp
@@ -34,12 +34,10 @@ bool TargetSubtargetInfo::isIntrinsicSupported(unsigned IntrinsicID) const {
   if (RequiredFeatures.empty())
     return true;
 
-  if (RequiredFeatures == Intrinsic::CustomTargetFeatures)
-    return false;
-
   auto [It, Inserted] = IntrinsicSupportCache.try_emplace(IntrinsicID);
   if (Inserted)
-    It->second = checkFeatureExpression(RequiredFeatures);
+    It->second = !RequiredFeatures.contains(Intrinsic::CustomTargetFeatures) &&
+                 checkFeatureExpression(RequiredFeatures);
   return It->second;
 }
 
@@ -59,9 +57,24 @@ TargetSubtargetInfo::getRequiredTargetFeaturesForIntrinsic(
     unsigned IntrinsicID, const FunctionType *FTy) const {
   StringRef RequiredFeatures = Intrinsic::getRequiredTargetFeatures(
       static_cast<Intrinsic::ID>(IntrinsicID));
-  if (RequiredFeatures == Intrinsic::CustomTargetFeatures)
-    return std::nullopt;
-  return RequiredFeatures;
+  if (!RequiredFeatures.contains(Intrinsic::CustomTargetFeatures))
+    return RequiredFeatures;
+
+  StringRef StaticRequiredFeatures =
+      RequiredFeatures == Intrinsic::CustomTargetFeatures
+          ? StringRef()
+          : RequiredFeatures.drop_back(Intrinsic::CustomTargetFeatures.size() +
+                                       1);
+  if (!StaticRequiredFeatures.empty() &&
+      !checkFeatureExpression(StaticRequiredFeatures))
+    return StaticRequiredFeatures;
+  return getCustomRequiredTargetFeaturesForIntrinsic(IntrinsicID, FTy);
+}
+
+std::optional<StringRef>
+TargetSubtargetInfo::getCustomRequiredTargetFeaturesForIntrinsic(
+    unsigned, const FunctionType *) const {
+  return std::nullopt;
 }
 
 bool TargetSubtargetInfo::enableAtomicExpand() const {
diff --git a/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp b/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp
index 5b206b4290e67..e49fd91f55ce2 100644
--- a/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp
+++ b/llvm/lib/Target/AMDGPU/GCNSubtarget.cpp
@@ -58,7 +58,8 @@ static cl::opt<unsigned>
 
 GCNSubtarget::~GCNSubtarget() = default;
 
-std::optional<StringRef> GCNSubtarget::getRequiredTargetFeaturesForIntrinsic(
+std::optional<StringRef>
+GCNSubtarget::getCustomRequiredTargetFeaturesForIntrinsic(
     unsigned IntrinsicID, const FunctionType *FTy) const {
   if (IntrinsicID == Intrinsic::amdgcn_ballot) {
     if (!FTy)
@@ -68,8 +69,8 @@ std::optional<StringRef> GCNSubtarget::getRequiredTargetFeaturesForIntrinsic(
     return StringRef();
   }
 
-  return TargetSubtargetInfo::getRequiredTargetFeaturesForIntrinsic(IntrinsicID,
-                                                                    FTy);
+  return TargetSubtargetInfo::getCustomRequiredTargetFeaturesForIntrinsic(
+      IntrinsicID, FTy);
 }
 
 static AMDGPUSubtarget::Generation computeDefaultGeneration(const Triple &TT) {
diff --git a/llvm/lib/Target/AMDGPU/GCNSubtarget.h b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
index 12811b21045a0..68b62383901db 100644
--- a/llvm/lib/Target/AMDGPU/GCNSubtarget.h
+++ b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
@@ -131,9 +131,8 @@ class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
   /// function \p F.
   void checkSubtargetFeatures(const Function &F) const;
 
-  std::optional<StringRef>
-  getRequiredTargetFeaturesForIntrinsic(unsigned IntrinsicID,
-                                        const FunctionType *FTy) const override;
+  std::optional<StringRef> getCustomRequiredTargetFeaturesForIntrinsic(
+      unsigned IntrinsicID, const FunctionType *FTy) const override;
 
   const SIInstrInfo *getInstrInfo() const override { return &InstrInfo; }
 
diff --git a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.wmma.target-features.ll b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.wmma.target-features.ll
index 3fc00787d1781..5685ecb8ef465 100644
--- a/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.wmma.target-features.ll
+++ b/llvm/test/CodeGen/AMDGPU/llvm.amdgcn.wmma.target-features.ll
@@ -1,12 +1,12 @@
 ; RUN: not llc -global-isel=0 -mtriple=amdgpu9.50 -filetype=null < %s 2>&1 | FileCheck %s
 ; RUN: not llc -global-isel=1 -global-isel-abort=0 -mtriple=amdgpu9.50 -filetype=null < %s 2>&1 | FileCheck %s
 ;
-; CHECK: llvm.amdgcn.wmma.f16.16x16x16.f16.tied requires target feature 'wmma-256b-insts'
-; CHECK: llvm.amdgcn.wmma.f32.16x16x16.f16 requires target feature 'wmma-256b-insts|wmma-128b-insts'
-; CHECK: llvm.amdgcn.wmma.f32.16x16x16.fp8.fp8 requires target feature 'wmma-128b-insts'
-; CHECK: llvm.amdgcn.wmma.f32.16x16x4.f32 requires target feature 'gfx1250-insts'
-; CHECK: llvm.amdgcn.wmma.f64.16x16x4.f64 requires target feature 'gfx1251-gemm-insts'
-; CHECK: llvm.amdgcn.wmma.f32.16x16x32.bf16 requires target feature 'wmma-n16-insts'
+; CHECK: llvm.amdgcn.wmma.f16.16x16x16.f16.tied.v16f16.v16f16 requires target feature 'wmma-256b-insts'
+; CHECK: llvm.amdgcn.wmma.f32.16x16x16.f16.v8f32.v16f16 requires target feature 'wmma-256b-insts|wmma-128b-insts'
+; CHECK: llvm.amdgcn.wmma.f32.16x16x16.fp8.fp8.v8f32.v2i32 requires target feature 'wmma-128b-insts'
+; CHECK: llvm.amdgcn.wmma.f32.16x16x4.f32.v8f32.v2f32 requires target feature 'gfx1250-insts'
+; CHECK: llvm.amdgcn.wmma.f64.16x16x4.f64.v8f64.v2f64 requires target feature 'gfx1251-gemm-insts'
+; CHECK: llvm.amdgcn.wmma.f32.16x16x32.bf16.v8f32.v16bf16 requires target feature 'wmma-n16-insts'
 
 define <16 x half> @wmma_256b(<16 x half> %a, <16 x half> %b, <16 x half> %c) {
   %result = call <16 x half> @llvm.amdgcn.wmma.f16.16x16x16.f16.tied(<16 x half> %a, <16 x half> %b, <16 x half> %c, i1 false)
diff --git a/llvm/test/TableGen/intrinsic-target-features.td b/llvm/test/TableGen/intrinsic-target-features.td
index c294bce53e102..d55f8a60338b2 100644
--- a/llvm/test/TableGen/intrinsic-target-features.td
+++ b/llvm/test/TableGen/intrinsic-target-features.td
@@ -1,11 +1,15 @@
 // RUN: llvm-tblgen -gen-intrinsic-impl -I %p/../../include -DTEST_INTRINSICS_SUPPRESS_DEFS %s | FileCheck %s
+// RUN: not llvm-tblgen -gen-intrinsic-impl -I %p/../../include -DTEST_INTRINSICS_SUPPRESS_DEFS -DINVALID_CUSTOM %s 2>&1 | FileCheck %s --check-prefix=ERR
 
 include "llvm/IR/Intrinsics.td"
 
 def int_no_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>;
 
-def int_custom_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>,
-  RequiresCustomTargetFeatures;
+let TargetFeatures = "$custom" in
+def int_custom_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>;
+
+let TargetFeatures = "feat-a,$custom" in
+def int_mixed_custom_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>;
 
 def int_requires_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>,
   RequiresTargetFeatures<"feat-a,(feat-b|feat-c)">;
@@ -13,6 +17,13 @@ def int_requires_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>,
 let TargetFeatures = "feat-let-a,feat-let-b" in
 def int_let_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>;
 
+#ifdef INVALID_CUSTOM
+let TargetFeatures = "$custom,feat-a" in
+def int_invalid_custom_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>;
+#endif
+
+// ERR: error: $custom must be the entire target feature expression or its final comma-separated term
+
 // CHECK:      #ifdef GET_INTRINSIC_TARGET_FEATURES_TABLE
 // CHECK-NEXT: #undef GET_INTRINSIC_TARGET_FEATURES_TABLE
 // CHECK:      // Intrinsic ID to required target features table.
@@ -20,6 +31,7 @@ def int_let_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>;
 // CHECK-NEXT: "\0"
 // CHECK-NEXT: "$custom\0"
 // CHECK-NEXT: "feat-let-a,feat-let-b\0"
+// CHECK-NEXT: "feat-a,$custom\0"
 // CHECK-NEXT: "feat-a,(feat-b|feat-c)\0"
 // CHECK:      static constexpr llvm::StringTable
 // CHECK-NEXT: IntrinsicTargetFeaturesTable = IntrinsicTargetFeaturesTableStorage;
@@ -27,6 +39,7 @@ def int_let_feature : Intrinsic<[llvm_i32_ty], [], [IntrNoMem]>;
 // CHECK-NEXT:   0, // not_intrinsic
 // CHECK-NEXT:   {{[0-9]+}}, // llvm.custom.feature
 // CHECK-NEXT:   {{[0-9]+}}, // llvm.let.feature
+// CHECK-NEXT:   {{[0-9]+}}, // llvm.mixed.custom.feature
 // CHECK-NEXT:   0, // llvm.no.feature
 // CHECK-NEXT:   {{[0-9]+}}, // llvm.requires.feature
 // CHECK:      }; // IntrinsicTargetFeaturesOffsetTable
diff --git a/llvm/utils/TableGen/Basic/CodeGenIntrinsics.cpp b/llvm/utils/TableGen/Basic/CodeGenIntrinsics.cpp
index 1d12235c87a24..ab698bbed6cb0 100644
--- a/llvm/utils/TableGen/Basic/CodeGenIntrinsics.cpp
+++ b/llvm/utils/TableGen/Basic/CodeGenIntrinsics.cpp
@@ -311,6 +311,18 @@ CodeGenIntrinsic::CodeGenIntrinsic(const Record *R,
   // Ignore a missing MSBuiltinName field.
   MSBuiltinName = R->getValueAsOptionalString("MSBuiltinName").value_or("");
   TargetFeatures = R->getValueAsString("TargetFeatures");
+  constexpr StringLiteral CustomTargetFeatures = "$custom";
+  constexpr StringLiteral CustomTargetFeaturesSuffix = ",$custom";
+  if (TargetFeatures.contains(CustomTargetFeatures) &&
+      TargetFeatures != CustomTargetFeatures) {
+    StringRef StaticFeatures = TargetFeatures;
+    if (!StaticFeatures.consume_back(CustomTargetFeaturesSuffix) ||
+        StaticFeatures.empty() || StaticFeatures.contains(CustomTargetFeatures))
+      PrintFatalError(
+          R->getLoc(),
+          "$custom must be the entire target feature expression or its final "
+          "comma-separated term");
+  }
 
   TargetPrefix = R->getValueAsString("TargetPrefix");
   Name = R->getValueAsString("LLVMName").str();



More information about the llvm-commits mailing list