[llvm] [MIR2Vec] Handle registers without a register class (PR #212280)
Petr Kurapov via llvm-commits
llvm-commits at lists.llvm.org
Tue Jul 28 07:34:21 PDT 2026
https://github.com/kurapov-peter updated https://github.com/llvm/llvm-project/pull/212280
>From 7f84a931e5f1785c4f44c7001f4b65150931ce0c Mon Sep 17 00:00:00 2001
From: Petr Kurapov <petr.kurapov at amd.com>
Date: Mon, 27 Jul 2026 10:18:15 -0500
Subject: [PATCH 1/4] [MIR2Vec] Handle registers without a register class
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.
---
llvm/include/llvm/CodeGen/MIR2Vec.h | 22 ++++++---
llvm/lib/CodeGen/MIR2Vec.cpp | 27 ++++++++---
.../Inputs/mir2vec_classless_vocab.json | 18 ++++++++
.../CodeGen/MIR2Vec/classless-physreg.mir | 38 ++++++++++++++++
llvm/test/CodeGen/MIR2Vec/classless-vreg.mir | 45 +++++++++++++++++++
5 files changed, 137 insertions(+), 13 deletions(-)
create mode 100644 llvm/test/CodeGen/MIR2Vec/Inputs/mir2vec_classless_vocab.json
create mode 100644 llvm/test/CodeGen/MIR2Vec/classless-physreg.mir
create mode 100644 llvm/test/CodeGen/MIR2Vec/classless-vreg.mir
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..c275bb9270c07 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,23 @@ 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.
+ 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..a00235e2b830c
--- /dev/null
+++ b/llvm/test/CodeGen/MIR2Vec/classless-physreg.mir
@@ -0,0 +1,38 @@
+# 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().
+
+--- |
+ 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..7881151eb52ff
--- /dev/null
+++ b/llvm/test/CodeGen/MIR2Vec/classless-vreg.mir
@@ -0,0 +1,45 @@
+# 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.
+
+--- |
+ 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 ]
>From 1e437872cd9687ee672747c257ccd334f7001bf0 Mon Sep 17 00:00:00 2001
From: Petr Kurapov <petr.kurapov at amd.com>
Date: Mon, 27 Jul 2026 11:50:33 -0500
Subject: [PATCH 2/4] format
---
llvm/lib/CodeGen/MIR2Vec.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/CodeGen/MIR2Vec.cpp b/llvm/lib/CodeGen/MIR2Vec.cpp
index c275bb9270c07..e247177966344 100644
--- a/llvm/lib/CodeGen/MIR2Vec.cpp
+++ b/llvm/lib/CodeGen/MIR2Vec.cpp
@@ -383,8 +383,8 @@ MIRVocabulary::getRegisterOperandIndex(Register Reg) const {
// RegisterBank instead of a TargetRegisterClass, for which
// getRegClassOrNull() returns nullptr.
if (!RegClass) {
- LLVM_DEBUG(errs() << "MIR2Vec: No register class for register "
- << Reg.id() << "; using zero vector.\n");
+ LLVM_DEBUG(errs() << "MIR2Vec: No register class for register " << Reg.id()
+ << "; using zero vector.\n");
++MIRClasslessRegCounter;
return std::nullopt;
}
>From 98fd23c5183c39e01485ec1c8c567fe1112ef9ee Mon Sep 17 00:00:00 2001
From: Petr Kurapov <petr.kurapov at amd.com>
Date: Tue, 28 Jul 2026 06:35:38 -0500
Subject: [PATCH 3/4] add a todo
---
llvm/lib/CodeGen/MIR2Vec.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/llvm/lib/CodeGen/MIR2Vec.cpp b/llvm/lib/CodeGen/MIR2Vec.cpp
index e247177966344..4f054bba883a4 100644
--- a/llvm/lib/CodeGen/MIR2Vec.cpp
+++ b/llvm/lib/CodeGen/MIR2Vec.cpp
@@ -382,6 +382,13 @@ MIRVocabulary::getRegisterOperandIndex(Register Reg) const {
// 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");
>From 2d233f8256312a96cd262a619939e5715ae13dbf Mon Sep 17 00:00:00 2001
From: Petr Kurapov <petr.kurapov at amd.com>
Date: Tue, 28 Jul 2026 09:34:07 -0500
Subject: [PATCH 4/4] add target triple to override hosts defaults
---
llvm/test/CodeGen/MIR2Vec/classless-physreg.mir | 2 ++
llvm/test/CodeGen/MIR2Vec/classless-vreg.mir | 2 ++
2 files changed, 4 insertions(+)
diff --git a/llvm/test/CodeGen/MIR2Vec/classless-physreg.mir b/llvm/test/CodeGen/MIR2Vec/classless-physreg.mir
index a00235e2b830c..a58770393a644 100644
--- a/llvm/test/CodeGen/MIR2Vec/classless-physreg.mir
+++ b/llvm/test/CodeGen/MIR2Vec/classless-physreg.mir
@@ -8,6 +8,8 @@
# same lookup through getEntityIDForRegister().
--- |
+ target triple = "x86_64-unknown-linux-gnu"
+
define double @classless_physreg(double %a, double %b) { ret double 0.0 }
...
---
diff --git a/llvm/test/CodeGen/MIR2Vec/classless-vreg.mir b/llvm/test/CodeGen/MIR2Vec/classless-vreg.mir
index 7881151eb52ff..f795daa115560 100644
--- a/llvm/test/CodeGen/MIR2Vec/classless-vreg.mir
+++ b/llvm/test/CodeGen/MIR2Vec/classless-vreg.mir
@@ -11,6 +11,8 @@
# vocabulary entry rather than crashing.
--- |
+ target triple = "x86_64-unknown-linux-gnu"
+
define i32 @g(i32 %a) { ret i32 0 }
...
---
More information about the llvm-commits
mailing list