[llvm] 1cfc77f - [MIR2Vec] Handle registers without a register class (#212280)

via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 28 08:16:28 PDT 2026


Author: Petr Kurapov
Date: 2026-07-28T15:16:23Z
New Revision: 1cfc77fdd6368dd6527c620645ec6f351e1c658f

URL: https://github.com/llvm/llvm-project/commit/1cfc77fdd6368dd6527c620645ec6f351e1c658f
DIFF: https://github.com/llvm/llvm-project/commit/1cfc77fdd6368dd6527c620645ec6f351e1c658f.diff

LOG: [MIR2Vec] Handle registers without a register class (#212280)

getRegisterOperandIndex() hit an llvm_unreachable for registers in no
register class. That is a supported outcome, not an error: tablegen
emits InvalidRegClassID for such registers and getMinimalPhysRegClass()
returns nullptr. It hits classless physregs (X86 $mxcsr/$fpcw, AMDGPU
$mode), which crashed any function with FP math, and generic vregs
before ISel, where MRI.getRegClass() asserts -- so use
getRegClassOrNull().

Return std::nullopt instead. Both callers already handle the analogous
NoRegister and stack-slot cases, so the vocabulary layout is unchanged.

We could avoid the special casing by adding a special value for such
registers. It would actually hold some meaningful information for
models. I didn't include it in this patch since it would require seed
vocab re-generation.

Added: 
    llvm/test/CodeGen/MIR2Vec/Inputs/mir2vec_classless_vocab.json
    llvm/test/CodeGen/MIR2Vec/classless-physreg.mir
    llvm/test/CodeGen/MIR2Vec/classless-vreg.mir

Modified: 
    llvm/include/llvm/CodeGen/MIR2Vec.h
    llvm/lib/CodeGen/MIR2Vec.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/CodeGen/MIR2Vec.h b/llvm/include/llvm/CodeGen/MIR2Vec.h
index 6f7b6dfce4378..7d936c6d3c8f5 100644
--- a/llvm/include/llvm/CodeGen/MIR2Vec.h
+++ b/llvm/include/llvm/CodeGen/MIR2Vec.h
@@ -54,6 +54,7 @@
 #include "llvm/Support/Error.h"
 #include "llvm/Support/ErrorOr.h"
 #include <map>
+#include <optional>
 #include <set>
 #include <string>
 
@@ -160,8 +161,10 @@ class MIRVocabulary {
   LLVM_ABI unsigned
   getCommonOperandIndex(MachineOperand::MachineOperandType OperandType) const;
 
-  /// Get index for a register machine operand
-  LLVM_ABI unsigned getRegisterOperandIndex(Register Reg) const;
+  /// Get index for a register machine operand. Returns std::nullopt if Reg
+  /// belongs to no register class, which is a valid outcome for some target
+  /// physical registers.
+  LLVM_ABI std::optional<unsigned> getRegisterOperandIndex(Register Reg) const;
 
   // Accessors for operand types
   const Embedding &
@@ -184,10 +187,14 @@ class MIRVocabulary {
     if (Reg.isStack())
       return ZeroEmbedding;
 
-    unsigned LocalIndex = getRegisterOperandIndex(Reg);
+    // Registers that belong to no register class have no vocabulary entry;
+    // treat them like the other unmapped cases above.
+    std::optional<unsigned> LocalIndex = getRegisterOperandIndex(Reg);
+    if (!LocalIndex)
+      return ZeroEmbedding;
     auto SectionID =
         Reg.isPhysical() ? Section::PhyRegisters : Section::VirtRegisters;
-    return Storage[static_cast<unsigned>(SectionID)][LocalIndex];
+    return Storage[static_cast<unsigned>(SectionID)][*LocalIndex];
   }
 
   /// Get entity ID (flat index) for a common operand type
@@ -203,10 +210,13 @@ class MIRVocabulary {
     if (!Reg.isValid() || Reg.isStack())
       return Layout
           .VirtRegBase; // Return VirtRegBase for invalid/stack registers
-    unsigned LocalIndex = getRegisterOperandIndex(Reg);
+    std::optional<unsigned> LocalIndex = getRegisterOperandIndex(Reg);
+    // Registers without a register class share the invalid/stack fallback.
+    if (!LocalIndex)
+      return Layout.VirtRegBase;
     size_t BaseOffset =
         Reg.isPhysical() ? Layout.PhyRegBase : Layout.VirtRegBase;
-    return BaseOffset + LocalIndex;
+    return BaseOffset + *LocalIndex;
   }
 
 public:

diff  --git a/llvm/lib/CodeGen/MIR2Vec.cpp b/llvm/lib/CodeGen/MIR2Vec.cpp
index 42e299834b77b..4f054bba883a4 100644
--- a/llvm/lib/CodeGen/MIR2Vec.cpp
+++ b/llvm/lib/CodeGen/MIR2Vec.cpp
@@ -29,6 +29,8 @@ using namespace mir2vec;
 
 STATISTIC(MIRVocabMissCounter,
           "Number of lookups to MIR entities not present in the vocabulary");
+STATISTIC(MIRClasslessRegCounter,
+          "Number of register operands with no register class");
 
 namespace llvm {
 namespace mir2vec {
@@ -356,7 +358,8 @@ unsigned MIRVocabulary::getCommonOperandIndex(
   return static_cast<unsigned>(OperandType) - 1;
 }
 
-unsigned MIRVocabulary::getRegisterOperandIndex(Register Reg) const {
+std::optional<unsigned>
+MIRVocabulary::getRegisterOperandIndex(Register Reg) const {
   assert(!RegisterOperandNames.empty() && "Register operand mapping not built");
   assert(Reg.isValid() && "Invalid register; not expected here");
   assert((Reg.isPhysical() || Reg.isVirtual()) &&
@@ -370,13 +373,30 @@ unsigned MIRVocabulary::getRegisterOperandIndex(Register Reg) const {
   if (Reg.isPhysical())
     RegClass = TRI.getMinimalPhysRegClass(Reg);
   else
-    RegClass = MRI.getRegClass(Reg);
+    RegClass = MRI.getRegClassOrNull(Reg);
+
+  // Not every register belongs to a register class. This can happen for
+  // physical registers, e.g. X86's $mxcsr and $fpcw or AMDGPU's $mode, for
+  // which getMinimalPhysRegClass() returns nullptr. It can also happen for
+  // generic virtual registers that have not yet been through (or completed)
+  // GlobalISel's register bank selection, and thus carry an LLT or a
+  // RegisterBank instead of a TargetRegisterClass, for which
+  // getRegClassOrNull() returns nullptr.
+  // TODO: Avoid special-casing these registers at every use site. Classless
+  // registers currently fall back to a zero embedding in operator[] and to
+  // VirtRegBase in getEntityIDForRegister(), which is the same ad-hoc handling
+  // the invalid/stack-slot cases already get. Give them a real vocabulary
+  // representation instead -- e.g. an explicit "no register class" entry, or
+  // keying generic vregs on their LLT/RegisterBank -- so that the lookup is
+  // total and the callers need no fallbacks.
+  if (!RegClass) {
+    LLVM_DEBUG(errs() << "MIR2Vec: No register class for register " << Reg.id()
+                      << "; using zero vector.\n");
+    ++MIRClasslessRegCounter;
+    return std::nullopt;
+  }
 
-  if (RegClass)
-    return RegClass->getID();
-  // Fallback for registers without a class (shouldn't happen)
-  llvm_unreachable("Register operand without a valid register class");
-  return 0;
+  return RegClass->getID();
 }
 
 Expected<MIRVocabulary> MIRVocabulary::createDummyVocabForTest(

diff  --git a/llvm/test/CodeGen/MIR2Vec/Inputs/mir2vec_classless_vocab.json b/llvm/test/CodeGen/MIR2Vec/Inputs/mir2vec_classless_vocab.json
new file mode 100644
index 0000000000000..03b13efa48d1e
--- /dev/null
+++ b/llvm/test/CodeGen/MIR2Vec/Inputs/mir2vec_classless_vocab.json
@@ -0,0 +1,18 @@
+{
+  "Opcodes": {
+    "ADDSDrr": [1.0, 2.0, 3.0],
+    "COPY": [0.5, 0.5, 0.5],
+    "RET": [0.9, 0.9, 0.9]
+  },
+  "CommonOperands": {
+    "Immediate": [0.01, 0.01, 0.01]
+  },
+  "PhysicalRegisters": {
+    "VR128": [0.7, 0.7, 0.7],
+    "FR64": [0.6, 0.6, 0.6]
+  },
+  "VirtualRegisters": {
+    "FR64": [0.1, 0.2, 0.3],
+    "VR128": [0.4, 0.4, 0.4]
+  }
+}

diff  --git a/llvm/test/CodeGen/MIR2Vec/classless-physreg.mir b/llvm/test/CodeGen/MIR2Vec/classless-physreg.mir
new file mode 100644
index 0000000000000..a58770393a644
--- /dev/null
+++ b/llvm/test/CodeGen/MIR2Vec/classless-physreg.mir
@@ -0,0 +1,40 @@
+# REQUIRES: x86-registered-target
+# RUN: llc -mtriple=x86_64-unknown-linux-gnu -run-pass=none -print-mir2vec -mir2vec-vocab-path=%S/Inputs/mir2vec_classless_vocab.json %s -o /dev/null 2>&1 | FileCheck %s
+# RUN: llvm-ir2vec triplets --mode=mir %s -o /dev/null
+
+# Some target physical registers do not belong to any register class.
+# TargetRegisterInfo::getMinimalPhysRegClass() can return nullptr for them.
+# The second RUN line covers the triplet-generation path, which reaches the
+# same lookup through getEntityIDForRegister().
+
+--- |
+  target triple = "x86_64-unknown-linux-gnu"
+
+  define double @classless_physreg(double %a, double %b) { ret double 0.0 }
+...
+---
+name:            classless_physreg
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: fr64 }
+  - { id: 1, class: fr64 }
+  - { id: 2, class: fr64 }
+body:             |
+  bb.0:
+    liveins: $xmm0, $xmm1
+
+    %1:fr64 = COPY $xmm1
+    %0:fr64 = COPY $xmm0
+    %2:fr64 = nofpexcept ADDSDrr %0, %1, implicit $mxcsr
+    $xmm0 = COPY %2
+    RET 0, $xmm0
+...
+
+# The vocabulary gives ADDSDrr = [1.0 2.0 3.0] and virtual fr64 = [0.1 0.2 0.3].
+# The ADDSDrr embedding is therefore the opcode plus its three fr64 register
+# operands, with the classless $mxcsr adding exactly zero:
+#   [1.0 2.0 3.0] + 3 * [0.1 0.2 0.3] = [1.3 2.6 3.9]
+
+# CHECK: MIR2Vec embeddings for machine function classless_physreg:
+# CHECK: Machine instruction: %2:fr64 = nofpexcept ADDSDrr %0:fr64(tied-def 0), %1:fr64, implicit $mxcsr
+# CHECK-NEXT:  [ 1.30  2.60  3.90 ]

diff  --git a/llvm/test/CodeGen/MIR2Vec/classless-vreg.mir b/llvm/test/CodeGen/MIR2Vec/classless-vreg.mir
new file mode 100644
index 0000000000000..f795daa115560
--- /dev/null
+++ b/llvm/test/CodeGen/MIR2Vec/classless-vreg.mir
@@ -0,0 +1,47 @@
+# REQUIRES: x86-registered-target
+# RUN: llc -mtriple=x86_64-unknown-linux-gnu -run-pass=none -print-mir2vec -mir2vec-vocab-path=%S/Inputs/mir2vec_classless_vocab.json %s -o /dev/null 2>&1 | FileCheck %s
+# RUN: llvm-ir2vec triplets --mode=mir %s -o /dev/null
+
+# Generic (GlobalISel) virtual registers also belong to no register class
+# until instruction selection has assigned one -- they instead carry an LLT
+# (printed as class `_`) or, after RegBankSelect, a register bank.
+# MachineRegisterInfo::getRegClass() asserts in that case, so
+# MIRVocabulary::getRegisterOperandIndex() must treat these the same way as
+# the classless physical registers in classless-physreg.mir: report no
+# vocabulary entry rather than crashing.
+
+--- |
+  target triple = "x86_64-unknown-linux-gnu"
+
+  define i32 @g(i32 %a) { ret i32 0 }
+...
+---
+name:            g
+legalized:       true
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: _ }
+body:             |
+  bb.0:
+    liveins: $edi
+
+    %0:_(s32) = COPY $edi
+    RET 0
+...
+
+# The vocabulary gives COPY = [0.5 0.5 0.5], RET = [0.9 0.9 0.9], and the
+# common "Immediate" operand = [0.01 0.01 0.01]. Neither the generic
+# (classless) vreg %0 nor $edi (a GR32 physical register, for which the
+# vocabulary also has no entry) contribute anything, so the COPY embedding is
+# exactly its opcode embedding. RET's "0" operand is an immediate, so its
+# embedding is the opcode plus the common Immediate operand embedding:
+#   [0.9 0.9 0.9] + [0.01 0.01 0.01] = [0.91 0.91 0.91]
+
+# CHECK: MIR2Vec embeddings for machine function g:
+# CHECK: Machine basic block: g:BB0:
+# CHECK-NEXT:  [ 1.41  1.41  1.41 ]
+# CHECK-NEXT: Machine instruction vectors:
+# CHECK-NEXT: Machine instruction: %0:_(s32) = COPY $edi
+# CHECK-NEXT:  [ 0.50  0.50  0.50 ]
+# CHECK-NEXT: Machine instruction: RET 0
+# CHECK-NEXT:  [ 0.91  0.91  0.91 ]


        


More information about the llvm-commits mailing list