[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