[llvm] [MIR] Save internal VirtRegMap state in MIR (PR #197361)

Quentin Colombet via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 12 07:09:35 PDT 2026


https://github.com/qcolombet updated https://github.com/llvm/llvm-project/pull/197361

>From f751df5ee0183492d358241fa3701b9f741f1c56 Mon Sep 17 00:00:00 2001
From: Quentin Colombet <quentin.colombet at gmail.com>
Date: Fri, 24 Apr 2026 15:37:47 -0700
Subject: [PATCH] [MIR] Save internal VirtRegMap state

Adds two optional fields to the per-vreg YAML record so MIR tests can
express VirtRegMap state that previously had no representation:

  registers:
    - { id: 1, class: vgpr_32, split-from: '%0', assigned-phys: '$vgpr5' }

Testing passes that consume sibling-register information (e.g.
InlineSpiller) requires constructing a VirtRegMap with split
relationships from a MIR test, which implies triggering live-range
splitting at minimum and make reproducers unnecessarily complicated.

So this change introduces a mechanism to serialize/deserialize the state
of the VirtRegMap pass.

Mechanism:
- For serialization:
  - MIRPrinter emits the new fields only when the VirtRegMap is available.
- For deserialization:
  - MIRParser stashes parsed entries in the MachineRegisterInfo object
  - VirtRegMap::init() drains the stash via assignVirt2Phys and
    setIsSplitFromReg, then clears it.

Validation at parse time:
  - 'assigned-phys' must be a physical register.
  - 'split-from' must reference a different vreg than 'id'.
  - 'split-from' physregs / named-vreg references already rejected
    by parseStandaloneVirtualRegister.

I'm not super happy about stashing the VRM info in the MRI, but that's
a small price to pay.
---
 llvm/include/llvm/CodeGen/MIRPrinter.h        | 11 ++++-
 llvm/include/llvm/CodeGen/MIRYamlMapping.h    | 17 +++++++-
 llvm/include/llvm/CodeGen/MachineFunction.h   |  7 +---
 .../llvm/CodeGen/MachineRegisterInfo.h        | 31 ++++++++++++++
 llvm/lib/CodeGen/MIRParser/MIRParser.cpp      | 40 +++++++++++++++++++
 llvm/lib/CodeGen/MIRPrinter.cpp               | 33 ++++++++++-----
 llvm/lib/CodeGen/MIRPrintingPass.cpp          | 11 ++++-
 llvm/lib/CodeGen/MachineFunction.cpp          | 16 ++++++++
 llvm/lib/CodeGen/VirtRegMap.cpp               | 13 ++++++
 .../MIR/AMDGPU/virtregmap-stash-bad-phys.mir  | 18 +++++++++
 .../MIR/AMDGPU/virtregmap-stash-bad-split.mir | 16 ++++++++
 .../AMDGPU/virtregmap-stash-self-split.mir    | 17 ++++++++
 .../CodeGen/MIR/AMDGPU/virtregmap-stash.mir   | 32 +++++++++++++++
 13 files changed, 242 insertions(+), 20 deletions(-)
 create mode 100644 llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash-bad-phys.mir
 create mode 100644 llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash-bad-split.mir
 create mode 100644 llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash-self-split.mir
 create mode 100644 llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash.mir

diff --git a/llvm/include/llvm/CodeGen/MIRPrinter.h b/llvm/include/llvm/CodeGen/MIRPrinter.h
index 829d16823d721..82d0ab1edbb4a 100644
--- a/llvm/include/llvm/CodeGen/MIRPrinter.h
+++ b/llvm/include/llvm/CodeGen/MIRPrinter.h
@@ -24,6 +24,7 @@ class MachineBasicBlock;
 class MachineFunction;
 class MachineModuleInfo;
 class Module;
+class VirtRegMap;
 template <typename T> class SmallVectorImpl;
 
 class PrintMIRPreparePass : public RequiredPassInfoMixin<PrintMIRPreparePass> {
@@ -47,12 +48,18 @@ class PrintMIRPass : public RequiredPassInfoMixin<PrintMIRPass> {
 LLVM_ABI void printMIR(raw_ostream &OS, const Module &M);
 
 /// Print MIR using Legacy Pass Manager (uses MachineModuleInfo).
+/// If \p VRM is non-null, the printer will serialize VirtRegMap state
+/// (split-from, assigned-phys).
 LLVM_ABI void printMIR(raw_ostream &OS, const MachineModuleInfo &MMI,
-                       const MachineFunction &MF);
+                       const MachineFunction &MF,
+                       const VirtRegMap *VRM = nullptr);
 
 /// Print MIR using New Pass Manager (uses FunctionAnalysisManager).
+/// If \p VRM is non-null, the printer will serialize VirtRegMap state
+/// (split-from, assigned-phys).
 LLVM_ABI void printMIR(raw_ostream &OS, FunctionAnalysisManager &FAM,
-                       const MachineFunction &MF);
+                       const MachineFunction &MF,
+                       const VirtRegMap *VRM = nullptr);
 
 /// Determine a possible list of successors of a basic block based on the
 /// basic block machine operand being used inside the block. This should give
diff --git a/llvm/include/llvm/CodeGen/MIRYamlMapping.h b/llvm/include/llvm/CodeGen/MIRYamlMapping.h
index cd0bcb656d34c..76f5e38a045ff 100644
--- a/llvm/include/llvm/CodeGen/MIRYamlMapping.h
+++ b/llvm/include/llvm/CodeGen/MIRYamlMapping.h
@@ -203,12 +203,19 @@ struct VirtualRegisterDefinition {
   StringValue Class;
   StringValue PreferredRegister;
   std::vector<FlowStringValue> RegisterFlags;
+  // VirtRegMap state.
+  // SplitFrom: id-form virtual register only (e.g. '%0'); physregs and named
+  //            vregs are rejected by the parser.
+  // AssignedPhys: physical register only (e.g. '$r5'); virtregs are rejected.
+  StringValue SplitFrom;
+  StringValue AssignedPhys;
 
   // TODO: Serialize the target specific register hints.
 
   bool operator==(const VirtualRegisterDefinition &Other) const {
     return ID == Other.ID && Class == Other.Class &&
-           PreferredRegister == Other.PreferredRegister;
+           PreferredRegister == Other.PreferredRegister &&
+           SplitFrom == Other.SplitFrom && AssignedPhys == Other.AssignedPhys;
   }
 };
 
@@ -220,6 +227,14 @@ template <> struct MappingTraits<VirtualRegisterDefinition> {
                        StringValue()); // Don't print out when it's empty.
     YamlIO.mapOptional("flags", Reg.RegisterFlags,
                        std::vector<FlowStringValue>());
+    // MIRPrinter sets WriteDefaultValues=true unless -simplify-mir is passed,
+    // so a plain mapOptional with an empty default would still emit the keys
+    // and change every existing test's output.
+    // Skip the call on output when empty to keep them off entirely.
+    if (!YamlIO.outputting() || !Reg.SplitFrom.Value.empty())
+      YamlIO.mapOptional("split-from", Reg.SplitFrom, StringValue());
+    if (!YamlIO.outputting() || !Reg.AssignedPhys.Value.empty())
+      YamlIO.mapOptional("assigned-phys", Reg.AssignedPhys, StringValue());
   }
 
   static const bool flow = true;
diff --git a/llvm/include/llvm/CodeGen/MachineFunction.h b/llvm/include/llvm/CodeGen/MachineFunction.h
index bd796fee2226f..b3c814e1c3590 100644
--- a/llvm/include/llvm/CodeGen/MachineFunction.h
+++ b/llvm/include/llvm/CodeGen/MachineFunction.h
@@ -901,12 +901,7 @@ class LLVM_ABI MachineFunction {
 
   MachineFunctionInfo *cloneInfoFrom(
       const MachineFunction &OrigMF,
-      const DenseMap<MachineBasicBlock *, MachineBasicBlock *> &Src2DstMBB) {
-    assert(!MFInfo && "new function already has MachineFunctionInfo");
-    if (!OrigMF.MFInfo)
-      return nullptr;
-    return OrigMF.MFInfo->clone(Allocator, *this, Src2DstMBB);
-  }
+      const DenseMap<MachineBasicBlock *, MachineBasicBlock *> &Src2DstMBB);
 
   /// Returns the denormal handling type for the default rounding mode of the
   /// function.
diff --git a/llvm/include/llvm/CodeGen/MachineRegisterInfo.h b/llvm/include/llvm/CodeGen/MachineRegisterInfo.h
index 2c29c8bbd0b31..166c701cf5e9b 100644
--- a/llvm/include/llvm/CodeGen/MachineRegisterInfo.h
+++ b/llvm/include/llvm/CodeGen/MachineRegisterInfo.h
@@ -65,6 +65,14 @@ class MachineRegisterInfo {
     }
   };
 
+  // VirtRegMap state parsed from MIR and waiting to be consumed by
+  // VirtRegMap::init().
+  struct PendingVirtRegMapEntry {
+    Register VReg;
+    Register SplitFrom;      // NoReg if absent.
+    MCRegister AssignedPhys; // NoReg if absent.
+  };
+
 private:
   MachineFunction *MF;
   SmallPtrSet<Delegate *, 1> TheDelegates;
@@ -107,6 +115,10 @@ class MachineRegisterInfo {
              VirtReg2IndexFunctor>
       RegAllocHints;
 
+  /// Hold the register properties that are used to populate the VirtRegMap
+  /// pass when deserializing from .mir files.
+  SmallVector<PendingVirtRegMapEntry, 0> PendingVirtRegMapEntries;
+
   /// PhysRegUseDefLists - This is an array of the head of the use/def list for
   /// physical registers.
   std::unique_ptr<MachineOperand *[]> PhysRegUseDefLists;
@@ -807,6 +819,25 @@ class MachineRegisterInfo {
   /// clearVirtRegs - Remove all virtual registers (after physreg assignment).
   LLVM_ABI void clearVirtRegs();
 
+  void addPendingVirtRegMapEntry(PendingVirtRegMapEntry Entry) {
+    assert(Entry.VReg.isVirtual());
+    assert(!Entry.SplitFrom.isValid() || Entry.SplitFrom.isVirtual());
+    assert(!Entry.AssignedPhys.isValid() || Entry.AssignedPhys.isPhysical());
+    PendingVirtRegMapEntries.push_back(Entry);
+  }
+
+  ArrayRef<PendingVirtRegMapEntry> getPendingVirtRegMapEntries() const {
+    return PendingVirtRegMapEntries;
+  }
+
+  void clearPendingVirtRegMapEntries() { PendingVirtRegMapEntries.clear(); }
+
+  void copyPendingVirtRegMapEntriesFrom(const MachineRegisterInfo &Other) {
+    assert(getNumVirtRegs() == Other.getNumVirtRegs() &&
+           "expected MachineFunction clone to preserve virtual registers");
+    PendingVirtRegMapEntries = Other.PendingVirtRegMapEntries;
+  }
+
   /// setRegAllocationHint - Specify a register allocation hint for the
   /// specified virtual register. This is typically used by target, and in case
   /// of an earlier hint it will be overwritten.
diff --git a/llvm/lib/CodeGen/MIRParser/MIRParser.cpp b/llvm/lib/CodeGen/MIRParser/MIRParser.cpp
index 00967e1de43cc..6f1e7594f34da 100644
--- a/llvm/lib/CodeGen/MIRParser/MIRParser.cpp
+++ b/llvm/lib/CodeGen/MIRParser/MIRParser.cpp
@@ -804,6 +804,46 @@ bool MIRParserImpl::parseRegisterInfo(PerFunctionMIParsingState &PFS,
     RegInfo.setCalleeSavedRegs(CalleeSavedRegisters);
   }
 
+  // Stash any VirtRegMap state on MRI.
+  // VirtRegMap::init() will use that information to get pre-populated
+  // on the first analysis run.
+  for (const auto &VReg : YamlMF.VirtualRegisters) {
+    if (VReg.SplitFrom.Value.empty() && VReg.AssignedPhys.Value.empty())
+      continue;
+
+    auto It = PFS.VRegInfos.find(VReg.ID.Value);
+    if (It == PFS.VRegInfos.end())
+      continue;
+    Register ChildReg = It->second->VReg;
+
+    MachineRegisterInfo::PendingVirtRegMapEntry Pending;
+    Pending.VReg = ChildReg;
+
+    if (!VReg.SplitFrom.Value.empty()) {
+      VRegInfo *Parent = nullptr;
+      if (parseVirtualRegisterReference(PFS, Parent, VReg.SplitFrom.Value,
+                                        Error))
+        return error(Error, VReg.SplitFrom.SourceRange);
+      if (Parent->VReg == ChildReg)
+        return error(VReg.SplitFrom.SourceRange.Start,
+                     Twine("'split-from' references the same vreg as 'id' (%") +
+                         Twine(VReg.ID.Value) + ")");
+      Pending.SplitFrom = Parent->VReg;
+    }
+    if (!VReg.AssignedPhys.Value.empty()) {
+      Register Phys;
+      if (parseRegisterReference(PFS, Phys, VReg.AssignedPhys.Value, Error))
+        return error(Error, VReg.AssignedPhys.SourceRange);
+      if (!Phys.isPhysical())
+        return error(
+            VReg.AssignedPhys.SourceRange.Start,
+            Twine("'assigned-phys' must be a physical register, got '") +
+                VReg.AssignedPhys.Value + "'");
+      Pending.AssignedPhys = Phys.asMCReg();
+    }
+    RegInfo.addPendingVirtRegMapEntry(Pending);
+  }
+
   return false;
 }
 
diff --git a/llvm/lib/CodeGen/MIRPrinter.cpp b/llvm/lib/CodeGen/MIRPrinter.cpp
index 679304c43af02..b65de01898fd5 100644
--- a/llvm/lib/CodeGen/MIRPrinter.cpp
+++ b/llvm/lib/CodeGen/MIRPrinter.cpp
@@ -37,6 +37,7 @@
 #include "llvm/CodeGen/TargetInstrInfo.h"
 #include "llvm/CodeGen/TargetRegisterInfo.h"
 #include "llvm/CodeGen/TargetSubtargetInfo.h"
+#include "llvm/CodeGen/VirtRegMap.h"
 #include "llvm/CodeGenTypes/LowLevelType.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/DebugLoc.h"
@@ -143,7 +144,7 @@ static void printMBB(raw_ostream &OS, MFPrintState &State,
                      const MachineBasicBlock &MBB);
 static void convertMRI(yaml::MachineFunction &YamlMF, const MachineFunction &MF,
                        const MachineRegisterInfo &RegInfo,
-                       const TargetRegisterInfo *TRI);
+                       const TargetRegisterInfo *TRI, const VirtRegMap *VRM);
 static void convertMCP(yaml::MachineFunction &MF,
                        const MachineConstantPool &ConstantPool);
 static void convertMJTI(ModuleSlotTracker &MST, yaml::MachineJumpTable &YamlJTI,
@@ -174,8 +175,8 @@ static void convertCalledGlobals(yaml::MachineFunction &YMF,
 static void convertPrefetchTargets(yaml::MachineFunction &YMF,
                                    const MachineFunction &MF);
 
-static void printMF(raw_ostream &OS, MFGetterFnT Fn,
-                    const MachineFunction &MF) {
+static void printMF(raw_ostream &OS, MFGetterFnT Fn, const MachineFunction &MF,
+                    const VirtRegMap *VRM) {
   MFPrintState State(std::move(Fn), MF);
 
   State.RegisterMaskIds = initRegisterMaskIds(MF);
@@ -206,7 +207,8 @@ static void printMF(raw_ostream &OS, MFGetterFnT Fn,
   YamlMF.IsSSA = Props.hasIsSSA();
   YamlMF.NoVRegs = Props.hasNoVRegs();
 
-  convertMRI(YamlMF, MF, MF.getRegInfo(), MF.getSubtarget().getRegisterInfo());
+  convertMRI(YamlMF, MF, MF.getRegInfo(), MF.getSubtarget().getRegisterInfo(),
+             VRM);
   MachineModuleSlotTracker &MST = State.MST;
   MST.incorporateFunction(MF.getFunction());
   convertMFI(MST, YamlMF.FrameInfo, MF.getFrameInfo(),
@@ -306,7 +308,7 @@ static void printRegFlags(Register Reg,
 
 static void convertMRI(yaml::MachineFunction &YamlMF, const MachineFunction &MF,
                        const MachineRegisterInfo &RegInfo,
-                       const TargetRegisterInfo *TRI) {
+                       const TargetRegisterInfo *TRI, const VirtRegMap *VRM) {
   YamlMF.TracksRegLiveness = RegInfo.tracksLiveness();
 
   // Print the virtual register definitions.
@@ -321,6 +323,17 @@ static void convertMRI(yaml::MachineFunction &YamlMF, const MachineFunction &MF,
     if (PreferredReg)
       printRegMIR(PreferredReg, VReg.PreferredRegister, TRI);
     printRegFlags(Reg, VReg.RegisterFlags, MF, TRI);
+    if (VRM) {
+      Register Orig = VRM->getPreSplitReg(Reg);
+      if (Orig && Orig != Reg) {
+        raw_string_ostream OS(VReg.SplitFrom.Value);
+        OS << printReg(Orig, TRI);
+      }
+      if (VRM->hasPhys(Reg)) {
+        raw_string_ostream OS(VReg.AssignedPhys.Value);
+        OS << printReg(VRM->getPhys(Reg), TRI);
+      }
+    }
     YamlMF.VirtualRegisters.push_back(std::move(VReg));
   }
 
@@ -1070,12 +1083,14 @@ void llvm::printMIR(raw_ostream &OS, const Module &M) {
 }
 
 void llvm::printMIR(raw_ostream &OS, const MachineModuleInfo &MMI,
-                    const MachineFunction &MF) {
-  printMF(OS, [&](const Function &F) { return MMI.getMachineFunction(F); }, MF);
+                    const MachineFunction &MF, const VirtRegMap *VRM) {
+  printMF(
+      OS, [&](const Function &F) { return MMI.getMachineFunction(F); }, MF,
+      VRM);
 }
 
 void llvm::printMIR(raw_ostream &OS, FunctionAnalysisManager &FAM,
-                    const MachineFunction &MF) {
+                    const MachineFunction &MF, const VirtRegMap *VRM) {
   printMF(
       OS,
       [&](const Function &F) {
@@ -1083,5 +1098,5 @@ void llvm::printMIR(raw_ostream &OS, FunctionAnalysisManager &FAM,
                        const_cast<Function &>(F))
                     .getMF();
       },
-      MF);
+      MF, VRM);
 }
diff --git a/llvm/lib/CodeGen/MIRPrintingPass.cpp b/llvm/lib/CodeGen/MIRPrintingPass.cpp
index a6fdaa6ab9c50..f5e455a520151 100644
--- a/llvm/lib/CodeGen/MIRPrintingPass.cpp
+++ b/llvm/lib/CodeGen/MIRPrintingPass.cpp
@@ -15,6 +15,7 @@
 #include "llvm/CodeGen/MachineFunctionPass.h"
 #include "llvm/CodeGen/MachineModuleInfo.h"
 #include "llvm/CodeGen/Passes.h"
+#include "llvm/CodeGen/VirtRegMap.h"
 #include "llvm/IR/Function.h"
 #include "llvm/InitializePasses.h"
 
@@ -30,7 +31,8 @@ PreservedAnalyses PrintMIRPass::run(MachineFunction &MF,
   auto &FAM = MFAM.getResult<FunctionAnalysisManagerMachineFunctionProxy>(MF)
                   .getManager();
 
-  printMIR(OS, FAM, MF);
+  const VirtRegMap *VRM = MFAM.getCachedResult<VirtRegMapAnalysis>(MF);
+  printMIR(OS, FAM, MF, VRM);
   return PreservedAnalyses::all();
 }
 
@@ -50,6 +52,7 @@ struct MIRPrintingPass : public MachineFunctionPass {
 
   void getAnalysisUsage(AnalysisUsage &AU) const override {
     AU.setPreservesAll();
+    AU.addUsedIfAvailable<VirtRegMapWrapperLegacy>();
     MachineFunctionPass::getAnalysisUsage(AU);
   }
 
@@ -60,7 +63,11 @@ struct MIRPrintingPass : public MachineFunctionPass {
     MachineModuleInfo *MMI =
         &getAnalysis<MachineModuleInfoWrapperPass>().getMMI();
 
-    printMIR(StrOS, *MMI, MF);
+    const VirtRegMap *VRM = nullptr;
+    if (auto *W = getAnalysisIfAvailable<VirtRegMapWrapperLegacy>())
+      VRM = &W->getVRM();
+
+    printMIR(StrOS, *MMI, MF, VRM);
     MachineFunctions.append(Str);
     return false;
   }
diff --git a/llvm/lib/CodeGen/MachineFunction.cpp b/llvm/lib/CodeGen/MachineFunction.cpp
index 49d8a19e5d126..2922922535333 100644
--- a/llvm/lib/CodeGen/MachineFunction.cpp
+++ b/llvm/lib/CodeGen/MachineFunction.cpp
@@ -264,6 +264,22 @@ void MachineFunction::initTargetMachineFunctionInfo(
   MFInfo = Target.createMachineFunctionInfo(Allocator, F, &STI);
 }
 
+MachineFunctionInfo *MachineFunction::cloneInfoFrom(
+    const MachineFunction &OrigMF,
+    const DenseMap<MachineBasicBlock *, MachineBasicBlock *> &Src2DstMBB) {
+  assert(!MFInfo && "new function already has MachineFunctionInfo");
+  if (!OrigMF.MFInfo)
+    return nullptr;
+
+  MachineFunctionInfo *ClonedInfo =
+      OrigMF.MFInfo->clone(Allocator, *this, Src2DstMBB);
+  if (!ClonedInfo)
+    return nullptr;
+
+  RegInfo->copyPendingVirtRegMapEntriesFrom(OrigMF.getRegInfo());
+  return ClonedInfo;
+}
+
 MachineFunction::~MachineFunction() {
   clear();
 }
diff --git a/llvm/lib/CodeGen/VirtRegMap.cpp b/llvm/lib/CodeGen/VirtRegMap.cpp
index 5a10a31050186..73e910027119e 100644
--- a/llvm/lib/CodeGen/VirtRegMap.cpp
+++ b/llvm/lib/CodeGen/VirtRegMap.cpp
@@ -74,6 +74,19 @@ void VirtRegMap::init(MachineFunction &mf) {
   Virt2ShapeMap.clear();
 
   grow();
+
+  // Drain any VirtRegMap state stashed by MIRParser on MRI. Must run after
+  // grow() so that Virt2*Map have capacity for every vreg referenced by the
+  // stash.
+  for (const auto &P : MRI->getPendingVirtRegMapEntries()) {
+    if (P.AssignedPhys.isValid())
+      assignVirt2Phys(P.VReg, P.AssignedPhys);
+    // Guard against self-reference; the parser also rejects this, but a
+    // belt-and-braces check keeps Virt2SplitMap meaningful.
+    if (P.SplitFrom.isValid() && P.SplitFrom != P.VReg)
+      setIsSplitFromReg(P.VReg, P.SplitFrom);
+  }
+  MRI->clearPendingVirtRegMapEntries();
 }
 
 void VirtRegMap::grow() {
diff --git a/llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash-bad-phys.mir b/llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash-bad-phys.mir
new file mode 100644
index 0000000000000..9dddfbf4fb6ca
--- /dev/null
+++ b/llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash-bad-phys.mir
@@ -0,0 +1,18 @@
+# RUN: not llc -mtriple=amdgcn -mcpu=gfx1100 -run-pass=virtregmap \
+# RUN:     -filetype=null %s 2>&1 | FileCheck %s
+#
+# 'assigned-phys' must be a physical register; passing a vreg should error.
+
+# CHECK: 'assigned-phys' must be a physical register, got '%0'
+---
+name: bad_phys
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: vgpr_32 }
+  - { id: 1, class: vgpr_32, assigned-phys: '%0' }
+body: |
+  bb.0:
+    %0:vgpr_32 = V_MOV_B32_e32 0, implicit $exec
+    %1:vgpr_32 = V_MOV_B32_e32 1, implicit $exec
+    S_ENDPGM 0, implicit %0, implicit %1
+...
diff --git a/llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash-bad-split.mir b/llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash-bad-split.mir
new file mode 100644
index 0000000000000..494677f23f7d1
--- /dev/null
+++ b/llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash-bad-split.mir
@@ -0,0 +1,16 @@
+# RUN: not llc -mtriple=amdgcn -mcpu=gfx1100 -run-pass=virtregmap \
+# RUN:     -filetype=null %s 2>&1 | FileCheck %s
+#
+# 'split-from' must reference a virtual register; physregs are rejected.
+
+# CHECK: expected a virtual register
+---
+name: bad_split
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: vgpr_32, split-from: '$vgpr5' }
+body: |
+  bb.0:
+    %0:vgpr_32 = V_MOV_B32_e32 0, implicit $exec
+    S_ENDPGM 0, implicit %0
+...
diff --git a/llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash-self-split.mir b/llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash-self-split.mir
new file mode 100644
index 0000000000000..a14c37e01b2e7
--- /dev/null
+++ b/llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash-self-split.mir
@@ -0,0 +1,17 @@
+# RUN: not llc -mtriple=amdgcn -mcpu=gfx1100 -run-pass=virtregmap \
+# RUN:     -filetype=null %s 2>&1 | FileCheck %s
+#
+# 'split-from' must reference a different vreg than 'id'.
+# I.e., disallow self-references
+
+# CHECK: 'split-from' references the same vreg as 'id' (%0)
+---
+name: self_split
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: vgpr_32, split-from: '%0' }
+body: |
+  bb.0:
+    %0:vgpr_32 = V_MOV_B32_e32 0, implicit $exec
+    S_ENDPGM 0, implicit %0
+...
diff --git a/llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash.mir b/llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash.mir
new file mode 100644
index 0000000000000..a9272c92775a6
--- /dev/null
+++ b/llvm/test/CodeGen/MIR/AMDGPU/virtregmap-stash.mir
@@ -0,0 +1,32 @@
+# RUN: llc -mtriple=amdgcn -mcpu=gfx1100 -run-pass=virtregmap \
+# RUN:     -o - %s | FileCheck %s
+#
+# Verify that VirtRegMap state (split-from, assigned-phys) round-trips through
+# MIR.
+
+# CHECK-LABEL: name: test_vrm_roundtrip
+# CHECK:      registers:
+# CHECK:      id: 0,
+# CHECK-NOT:  split-from:
+# CHECK:      assigned-phys: '$vgpr5'
+# CHECK:      id: 1,
+# CHECK:      split-from: '%0'
+# CHECK:      assigned-phys: '$vgpr6'
+# CHECK:      id: 2,
+# CHECK:      split-from: '%0'
+# CHECK:      assigned-phys: '$vgpr7'
+---
+name: test_vrm_roundtrip
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: vgpr_32, assigned-phys: '$vgpr5' }
+  - { id: 1, class: vgpr_32, assigned-phys: '$vgpr6', split-from: '%0' }
+  - { id: 2, class: vgpr_32, assigned-phys: '$vgpr7', split-from: '%0' }
+body: |
+  bb.0:
+    liveins: $vgpr0
+    %0:vgpr_32 = COPY $vgpr0
+    %1:vgpr_32 = V_ADD_U32_e32 1, %0:vgpr_32, implicit $exec
+    %2:vgpr_32 = V_ADD_U32_e32 2, %0:vgpr_32, implicit $exec
+    S_ENDPGM 0, implicit %0, implicit %1, implicit %2
+...



More information about the llvm-commits mailing list