Skip to content

Conversation

@arichardson
Copy link
Member

This is useful for InstAlias where a fixed register may depend on the
HwMode. The motivating use case for this is the RISC-V RVY ISA where
certain instructions mnemonics are remapped to take a different
register class depending on the HwMode and can be used as follows:

def NullReg : RegisterByHwMode<PtrRC, [RV32I, RV64I, RV64Y, RV64Y],
                                      [X0,    X0,    X0_Y,  X0_Y]>;

Created using spr 1.3.8-beta.1
@llvmbot llvmbot added tablegen llvm:SelectionDAG SelectionDAGISel as well labels Jan 9, 2026
@llvmbot
Copy link
Member

llvmbot commented Jan 9, 2026

@llvm/pr-subscribers-tablegen

@llvm/pr-subscribers-llvm-selectiondag

Author: Alexander Richardson (arichardson)

Changes

This is useful for InstAlias where a fixed register may depend on the
HwMode. The motivating use case for this is the RISC-V RVY ISA where
certain instructions mnemonics are remapped to take a different
register class depending on the HwMode and can be used as follows:

def NullReg : RegisterByHwMode&lt;PtrRC, [RV32I, RV64I, RV64Y, RV64Y],
                                      [X0,    X0,    X0_Y,  X0_Y]&gt;;

Patch is 57.26 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/175227.diff

19 Files Affected:

  • (modified) llvm/include/llvm/Target/Target.td (+10)
  • (added) llvm/test/TableGen/Common/RegisterByHwModeCommon.td (+92)
  • (modified) llvm/test/TableGen/RegClassByHwModeAlias.td (+35-6)
  • (modified) llvm/test/TableGen/RegClassByHwModeCompressPat.td (+54-4)
  • (modified) llvm/test/TableGen/RegClassByHwModeErrors.td (+1-2)
  • (added) llvm/test/TableGen/RegisterByHwMode.td (+316)
  • (added) llvm/test/TableGen/RegisterByHwModeErrors.td (+69)
  • (modified) llvm/utils/TableGen/AsmMatcherEmitter.cpp (+16-5)
  • (modified) llvm/utils/TableGen/AsmWriterEmitter.cpp (+36-9)
  • (modified) llvm/utils/TableGen/Common/CodeGenDAGPatterns.cpp (+1-1)
  • (modified) llvm/utils/TableGen/Common/CodeGenInstAlias.cpp (+5-1)
  • (modified) llvm/utils/TableGen/Common/CodeGenRegisters.cpp (+31)
  • (modified) llvm/utils/TableGen/Common/CodeGenRegisters.h (+7)
  • (modified) llvm/utils/TableGen/Common/InfoByHwMode.cpp (+52-1)
  • (modified) llvm/utils/TableGen/Common/InfoByHwMode.h (+12-2)
  • (modified) llvm/utils/TableGen/CompressInstEmitter.cpp (+27-12)
  • (modified) llvm/utils/TableGen/DAGISelMatcherGen.cpp (+4)
  • (modified) llvm/utils/TableGen/PseudoLoweringEmitter.cpp (+9-2)
  • (modified) llvm/utils/TableGen/SubtargetEmitter.cpp (+1)
diff --git a/llvm/include/llvm/Target/Target.td b/llvm/include/llvm/Target/Target.td
index 315de55b75510..61388c4902eb4 100644
--- a/llvm/include/llvm/Target/Target.td
+++ b/llvm/include/llvm/Target/Target.td
@@ -1078,6 +1078,16 @@ class RegisterOperand<RegisterClassLike regclass, string pm = "printOperand">
   Register GIZeroRegister = ?;
 }
 
+/// RegisterByHwMode - Useful for InstAliases with a hardcoded register operand
+/// where the operand type is a RegClassByHwMode.
+class RegisterByHwMode<RegisterClassLike RegClass, list<HwMode> Modes,
+                       list<Register> Registers>
+    : HwModeSelect<Modes, !size(Registers)>, RegisterOperand<RegClass> {
+  list<Register> Objects = Registers;
+  // Note: No assertions to validate the registers against the
+  // register class here, this is done inside llvm-tblgen.
+}
+
 let OperandType = "OPERAND_IMMEDIATE" in {
 def i1imm  : Operand<i1>;
 def i8imm  : Operand<i8>;
diff --git a/llvm/test/TableGen/Common/RegisterByHwModeCommon.td b/llvm/test/TableGen/Common/RegisterByHwModeCommon.td
new file mode 100644
index 0000000000000..57ec5b2d616c8
--- /dev/null
+++ b/llvm/test/TableGen/Common/RegisterByHwModeCommon.td
@@ -0,0 +1,92 @@
+include "llvm/Target/Target.td"
+
+/// A minimal reduced version of the RISC-V RVY register variants where pointers
+/// use either Xn or Xn_Y registers depending on CapMode and 64-bit predicates.
+/// Define HWModes for the full cross-product here since we can only match one
+/// HWMode at any given time.
+def Is32Bit : Predicate<"!Subtarget->is64Bit()">;
+def Is64Bit : Predicate<"Subtarget->is64Bit()">;
+def UseYRegForPtr : Predicate<"Subtarget->useYRegForPtr()">;
+def UseXRegForPtr : Predicate<"!Subtarget->useYRegForPtr()">;
+defvar XPtr32 = DefaultMode;
+def XPtr64 : HwMode<[Is64Bit, UseXRegForPtr]>;
+def YPtr32 : HwMode<[Is32Bit, UseYRegForPtr]>;
+def YPtr64 : HwMode<[Is64Bit, UseYRegForPtr]>;
+
+class MyReg<string n> : Register<n> {
+  let Namespace = "MyTarget";
+}
+
+def X0 : MyReg<"x0">;
+def X1 : MyReg<"x1">;
+def X2 : MyReg<"x2">;
+def X3 : MyReg<"x3">;
+
+def Y0 : MyReg<"y0">;
+def Y1 : MyReg<"y1">;
+def Y2 : MyReg<"y2">;
+def Y3 : MyReg<"y3">;
+
+def XLenVT : ValueTypeByHwMode<[XPtr32, XPtr64, YPtr32, YPtr64],
+                               [i32,    i64,    i32,    i64]>;
+def YLenVT : ValueTypeByHwMode<[XPtr32, XPtr64, YPtr32, YPtr64],
+                               [c64,    c128,   c64,    c128]>;
+def PtrVT : ValueTypeByHwMode<[XPtr32, XPtr64, YPtr32, YPtr64],
+                              [XLenVT, XLenVT, YLenVT, YLenVT]>;
+defvar RegInfo32 = RegInfo<32,32,32>;
+defvar RegInfo64 = RegInfo<64,64,64>;
+def XLenRI : RegInfoByHwMode<[XPtr32,    XPtr64,    YPtr32,    YPtr64],
+                             [RegInfo32, RegInfo32, RegInfo64, RegInfo64]>;
+def XRegs : RegisterClass<"MyTarget", [XLenVT], 32, (add X0, X1, X2, X3)> {
+  let RegInfos = XLenRI;  // Needed to determine size of registers
+}
+defvar RegInfo128 = RegInfo<128,128,128>;
+def YLenRI : RegInfoByHwMode<[XPtr32,    XPtr64,    YPtr32,     YPtr64],
+                             [RegInfo64, RegInfo64, RegInfo128, RegInfo128]>;
+
+def YRegs : RegisterClass<"MyTarget", [YLenVT], 64, (add Y0, Y1, Y2, Y3)> {
+  let RegInfos = YLenRI;  // Needed to determine size of registers
+}
+def PtrRC : RegClassByHwMode<[XPtr32, XPtr64, YPtr32, YPtr64],
+                             [XRegs,  XRegs,  YRegs,  YRegs]>;
+
+def PtrRegOperand : RegisterOperand<PtrRC>;
+
+def NullReg : RegisterByHwMode<PtrRC, [XPtr32, XPtr64, YPtr32, YPtr64],
+                                      [X0,     X0,     Y0,     Y0]>;
+
+class TestInstruction : Instruction {
+  let Size = 2;
+  let Namespace = "MyTarget";
+  let hasSideEffects = false;
+  let hasExtraSrcRegAllocReq = false;
+  let hasExtraDefRegAllocReq = false;
+
+  field bits<16> Inst;
+  bits<3> dst;
+  bits<3> src;
+  bits<3> opcode;
+
+  let Inst{2-0} = dst;
+  let Inst{5-3} = src;
+  let Inst{7-5} = opcode;
+}
+
+def TEST_XREG : TestInstruction {
+  let OutOperandList = (outs XRegs:$dst);
+  let InOperandList = (ins XRegs:$src);
+  let AsmString = "test_x $dst, $src";
+  let opcode = 0;
+}
+def TEST_YREG : TestInstruction {
+  let OutOperandList = (outs YRegs:$dst);
+  let InOperandList = (ins YRegs:$src);
+  let AsmString = "test_y $dst, $src";
+  let opcode = 1;
+}
+def TEST_PTRREG : TestInstruction {
+  let OutOperandList = (outs PtrRegOperand:$dst);
+  let InOperandList = (ins PtrRegOperand:$src);
+  let AsmString = "test_ptr $dst, $src";
+  let opcode = 2;
+}
\ No newline at end of file
diff --git a/llvm/test/TableGen/RegClassByHwModeAlias.td b/llvm/test/TableGen/RegClassByHwModeAlias.td
index 726bd3a0a5a49..09d57974a70b3 100644
--- a/llvm/test/TableGen/RegClassByHwModeAlias.td
+++ b/llvm/test/TableGen/RegClassByHwModeAlias.td
@@ -11,6 +11,7 @@ def EvenXRegs : RegisterClass<"MyTarget", [i64], 64, (add X0, X2, X4, X6)>;
 def EvenYRegs : RegisterClass<"MyTarget", [i64], 64, (add Y0, Y2, Y4, Y6)>;
 def PtrRC : RegClassByHwMode<[PtrX, PtrY], [XRegs, YRegs]>;
 def EvenPtrRC : RegClassByHwMode<[PtrX, PtrY], [EvenXRegs, EvenYRegs]>;
+def NullReg : RegisterByHwMode<PtrRC, [PtrX, PtrY], [X0, Y0]>;
 
 def TEST_XREG : TestInstruction {
   let OutOperandList = (outs XRegs:$dst);
@@ -28,21 +29,49 @@ def TEST_PTR : TestInstruction {
 def MY_T_X : InstAlias<"t_x $src", (TEST_XREG X0, XRegs:$src)>;
 def MY_T_X_EVEN : InstAlias<"t_x.even $src", (TEST_XREG EvenXRegs:$dst, EvenXRegs:$src)>;
 
-// TODO: Can't use a fixed register for this instruction, would need RegisterByHwMode.
-// def MY_T_PTR : InstAlias<"t_ptr $src", (TEST_PTR X0, XRegs:$src)>;
+def MY_T_PTR : InstAlias<"t_ptr $src", (TEST_PTR NullReg, PtrRC:$src)>;
 def MY_T_PTR_EVEN : InstAlias<"t_ptr.even $src", (TEST_PTR EvenPtrRC:$dst, EvenPtrRC:$src)>;
 
 // CHECK-LABEL: static const AliasPatternCond Conds[] = {
-// CHECK-NEXT:    // (TEST_PTR EvenPtrRC:$dst, EvenPtrRC:$src) - 0
+// CHECK-NEXT:    // (TEST_PTR NullReg, PtrRC:$src) - 0
+// CHECK-NEXT:    {AliasPatternCond::K_Custom, 1/*NullReg*/},
+// CHECK-NEXT:    {AliasPatternCond::K_RegClassByHwMode, MyTarget::PtrRC},
+// CHECK-NEXT:    // (TEST_PTR EvenPtrRC:$dst, EvenPtrRC:$src) - 2
 // CHECK-NEXT:    {AliasPatternCond::K_RegClassByHwMode, MyTarget::EvenPtrRC},
 // CHECK-NEXT:    {AliasPatternCond::K_RegClassByHwMode, MyTarget::EvenPtrRC},
-// CHECK-NEXT:    // (TEST_XREG X0, XRegs:$src) - 2
+// CHECK-NEXT:    // (TEST_XREG X0, XRegs:$src) - 4
 // CHECK-NEXT:    {AliasPatternCond::K_Reg, MyTarget::X0},
 // CHECK-NEXT:    {AliasPatternCond::K_RegClass, MyTarget::XRegsRegClassID},
-// CHECK-NEXT:    // (TEST_XREG EvenXRegs:$dst, EvenXRegs:$src) - 4
+// CHECK-NEXT:    // (TEST_XREG EvenXRegs:$dst, EvenXRegs:$src) - 6
 // CHECK-NEXT:    {AliasPatternCond::K_RegClass, MyTarget::EvenXRegsRegClassID},
 // CHECK-NEXT:    {AliasPatternCond::K_RegClass, MyTarget::EvenXRegsRegClassID},
 // CHECK-NEXT:  };
 
+// CHECK-LABEL: static bool MyTargetInstPrinterValidateMCOperand(const MCOperand &MCOp,
+// CHECK-NEXT:                    const MCSubtargetInfo &STI,
+// CHECK-NEXT:                    unsigned PredicateIndex) {
+// CHECK-NEXT:    switch (PredicateIndex) {
+// CHECK-NEXT:    default:
+// CHECK-NEXT:      llvm_unreachable("Unknown MCOperandPredicate kind");
+// CHECK-NEXT:      break;
+// CHECK-NEXT:    case 1: {
+// CHECK-NEXT:      auto getNullReg = [](unsigned HwMode) {
+// CHECK-NEXT:        switch (HwMode) {
+// CHECK-NEXT:        case 0: return MyTarget::X0; // DefaultMode
+// CHECK-NEXT:        case 1: return MyTarget::Y0; // PtrY
+// CHECK-NEXT:        default: llvm_unreachable("Unhandled HwMode for Register NullReg");
+// CHECK-NEXT:        }
+// CHECK-NEXT:      };
+// CHECK-NEXT:      return MCOp.isReg() && MCOp.getReg() == getNullReg(STI.getHwMode(MCSubtargetInfo::HwMode_RegInfo));
+// CHECK-NEXT:    }
+// CHECK-NEXT:    }
+// CHECK-NEXT:  }
+
 def MyTargetISA : InstrInfo;
-def MyTarget : Target { let InstructionSet = MyTargetISA; }
+def MyTargetAsmWriter : AsmWriter {
+  int PassSubtarget = 1;
+}
+def MyTarget : Target {
+  let InstructionSet = MyTargetISA;
+  let AssemblyWriters = [MyTargetAsmWriter];
+}
diff --git a/llvm/test/TableGen/RegClassByHwModeCompressPat.td b/llvm/test/TableGen/RegClassByHwModeCompressPat.td
index d642584e52b37..f2b12aac9e008 100644
--- a/llvm/test/TableGen/RegClassByHwModeCompressPat.td
+++ b/llvm/test/TableGen/RegClassByHwModeCompressPat.td
@@ -6,7 +6,7 @@ def IsPtr64 : Predicate<"Subtarget->isPtr64()">;
 defvar Ptr32 = DefaultMode;
 def Ptr64 : HwMode<[IsPtr64]>;
 def PtrRC : RegClassByHwMode<[Ptr32, Ptr64], [XRegs, YRegs]>;
-
+def NullReg : RegisterByHwMode<PtrRC, [Ptr32, Ptr64], [X0, Y0]>;
 
 def X_MOV : TestInstruction {
   let OutOperandList = (outs XRegs:$dst);
@@ -74,9 +74,8 @@ def : CompressPat<(X_MOV XRegs:$dst, XRegs:$dst),
                   (X_MOV_TIED XRegs:$dst)>;
 def : CompressPat<(X_MOV XRegs:$dst, XRegs:$src),
                   (X_MOV_SMALL XRegs:$dst, XRegs:$src)>;
-// TODO: Should also be able to use a fixed register with RegClassByHwMode
-// def : CompressPat<(PTR_MOV PtrRC:$dst, X0),
-//                   (PTR_MOV_ZERO PtrRC:$dst)>;
+def : CompressPat<(PTR_MOV PtrRC:$dst, NullReg),
+                  (PTR_MOV_ZERO PtrRC:$dst)>;
 def : CompressPat<(PTR_MOV PtrRC:$dst, PtrRC:$dst),
                   (PTR_MOV_TIED PtrRC:$dst)>;
 def : CompressPat<(PTR_MOV PtrRC:$dst, PtrRC:$src),
@@ -89,6 +88,23 @@ def : CompressPat<(PTR_MOV PtrRC:$dst, PtrRC:$src),
 // CHECK-NEXT:   switch (MI.getOpcode()) {
 // CHECK-NEXT:   default: return false;
 // CHECK-NEXT:   case MyTarget::PTR_MOV: {
+// CHECK-NEXT:     if (MI.getOperand(1).isReg() &&
+// CHECK-NEXT:         (MI.getOperand(1).getReg() == [](unsigned HwMode) {
+// CHECK-NEXT:           switch (HwMode)
+// CHECK-NEXT:           case 0: return MyTarget::X0; // DefaultMode
+// CHECK-NEXT:           case 1: return MyTarget::Y0; // Ptr64
+// CHECK-NEXT:           default: llvm_unreachable("Unhandled HwMode for Register NullReg");
+// CHECK-NEXT:           }
+// CHECK-NEXT:         }(HwModeId)) &&
+// CHECK-NEXT:         MI.getOperand(0).isReg() &&
+// CHECK-NEXT:         MyTargetMCRegisterClasses[MyTargetRegClassByHwModeTables[HwModeId][MyTarget::PtrRC]].contains(MI.getOperand(0).getReg())) {
+// CHECK-NEXT:       // ptr_mov.zero $dst
+// CHECK-NEXT:       OutInst.setOpcode(MyTarget::PTR_MOV_ZERO);
+// CHECK-NEXT:       // Operand: dst
+// CHECK-NEXT:       OutInst.addOperand(MI.getOperand(0));
+// CHECK-NEXT:       OutInst.setLoc(MI.getLoc());
+// CHECK-NEXT:       return true;
+// CHECK-NEXT:     } // if
 // CHECK-NEXT:     if (MI.getOperand(1).isReg() && MI.getOperand(0).isReg() &&
 // CHECK-NEXT:         (MI.getOperand(1).getReg() == MI.getOperand(0).getReg()) &&
 // CHECK-NEXT:         MI.getOperand(1).isReg() &&
@@ -199,6 +215,26 @@ def : CompressPat<(PTR_MOV PtrRC:$dst, PtrRC:$src),
 // CHECK-NEXT:     } // if
 // CHECK-NEXT:     break;
 // CHECK-NEXT:   } // case PTR_MOV_TIED
+// CHECK-NEXT:   case MyTarget::PTR_MOV_ZERO: {
+// CHECK-NEXT:     if (MI.getOperand(0).isReg() &&
+// CHECK-NEXT:         MyTargetMCRegisterClasses[MyTargetRegClassByHwModeTables[HwModeId][MyTarget::PtrRC]].contains(MI.getOperand(0).getReg())) {
+// CHECK-NEXT:       // ptr_mov $dst, $src
+// CHECK-NEXT:       OutInst.setOpcode(MyTarget::PTR_MOV);
+// CHECK-NEXT:       // Operand: dst
+// CHECK-NEXT:       OutInst.addOperand(MI.getOperand(0));
+// CHECK-NEXT:       // Operand: src
+// CHECK-NEXT:       OutInst.addOperand(MCOperand::createReg([](unsigned HwMode) {
+// CHECK-NEXT:           switch (HwMode) {
+// CHECK-NEXT:           case 0: return MyTarget::X0; // DefaultMode
+// CHECK-NEXT:           case 1: return MyTarget::Y0; // Ptr64
+// CHECK-NEXT:           default: llvm_unreachable("Unhandled HwMode for Register NullReg");
+// CHECK-NEXT:           }
+// CHECK-NEXT:         }(HwModeId)));
+// CHECK-NEXT:       OutInst.setLoc(MI.getLoc());
+// CHECK-NEXT:       return true;
+// CHECK-NEXT:     } // if
+// CHECK-NEXT:     break;
+// CHECK-NEXT:   } // case PTR_MOV_ZERO
 // CHECK-NEXT:   case MyTarget::X_MOV_SMALL: {
 // CHECK-NEXT:     if (MI.getOperand(0).isReg() &&
 // CHECK-NEXT:         MyTargetMCRegisterClasses[MyTarget::XRegsRegClassID].contains(MI.getOperand(0).getReg()) &&
@@ -273,6 +309,20 @@ def : CompressPat<(PTR_MOV PtrRC:$dst, PtrRC:$src),
 // CHECK-NEXT:       // Operand: src
 // CHECK-NEXT:       return true;
 // CHECK-NEXT:     } // if
+// CHECK-NEXT:     if (MI.getOperand(1).isReg() &&
+// CHECK-NEXT:         (MI.getOperand(1).getReg() == [](unsigned HwMode) {
+// CHECK-NEXT:           switch (HwMode)
+// CHECK-NEXT:           case 0: return MyTarget::X0; // DefaultMode
+// CHECK-NEXT:           case 1: return MyTarget::Y0; // Ptr64
+// CHECK-NEXT:           default: llvm_unreachable("Unhandled HwMode for Register NullReg");
+// CHECK-NEXT:           }
+// CHECK-NEXT:         }(HwModeId)) &&
+// CHECK-NEXT:         MI.getOperand(0).isReg() && MI.getOperand(0).getReg().isPhysical() &&
+// CHECK-NEXT:         MyTargetMCRegisterClasses[MyTargetRegClassByHwModeTables[HwModeId][MyTarget::PtrRC]].contains(MI.getOperand(0).getReg())) {
+// CHECK-NEXT:       // ptr_mov.zero $dst
+// CHECK-NEXT:       // Operand: dst
+// CHECK-NEXT:       return true;
+// CHECK-NEXT:     } // if
 // CHECK-NEXT:     break;
 // CHECK-NEXT:   } // case PTR_MOV
 // CHECK-NEXT:   case MyTarget::X_MOV: {
diff --git a/llvm/test/TableGen/RegClassByHwModeErrors.td b/llvm/test/TableGen/RegClassByHwModeErrors.td
index c7731312e28a6..833224074ac34 100644
--- a/llvm/test/TableGen/RegClassByHwModeErrors.td
+++ b/llvm/test/TableGen/RegClassByHwModeErrors.td
@@ -66,8 +66,7 @@ def PTR_ZERO_SMALL : TestInstruction {
 /// This should fail since X0 is not necessarily part of PtrRC.
 def : CompressPat<(PTR_MOV PtrRC:$dst, X0),
                   (PTR_ZERO_SMALL PtrRC:$dst)>;
-// CHECK: [[#@LINE-2]]:1: error: cannot resolve HwMode for PtrRC
-// CHECK: Common.td:7:5: note: PtrRC defined here
+// CHECK: [[#@LINE-2]]:1: error: Error in Dag '(PTR_MOV PtrRC:$dst, X0)': Register 'X0' is not in register class 'PtrRC'
 def MyTargetISA : InstrInfo;
 def MyTarget : Target { let InstructionSet = MyTargetISA; }
 
diff --git a/llvm/test/TableGen/RegisterByHwMode.td b/llvm/test/TableGen/RegisterByHwMode.td
new file mode 100644
index 0000000000000..5a128a3b6d849
--- /dev/null
+++ b/llvm/test/TableGen/RegisterByHwMode.td
@@ -0,0 +1,316 @@
+// RUN: llvm-tblgen --gen-asm-matcher -I %p/../../include -I %S %s -o - | FileCheck --check-prefix=ASMMATCHER %s
+// RUN: llvm-tblgen --gen-pseudo-lowering -I %p/../../include -I %S %s -o - | FileCheck --check-prefix=PSEUDO %s
+// RUN: llvm-tblgen --gen-subtarget -I %p/../../include -I %S %s -o - | FileCheck --check-prefix=SUBTARGET %s
+// RUN: llvm-tblgen --gen-instr-info -I %p/../../include -I %S %s -o - | FileCheck --check-prefix=INSTRINFO %s
+// RUN: llvm-tblgen --gen-asm-writer -I %p/../../include -I %S %s -o - | FileCheck --check-prefix=ASMWRITER %s
+/// Note: No impact on disassembler (handled by the alias expansion), so not tested here
+/// Note: DAGIsel is not supported yet
+// RUNTODO: llvm-tblgen --gen-dag-isel -I %p/../../include -I %S %s -o -
+// RUNTODO: llvm-tblgen --gen-global-isel -I %p/../../include -I %S %s -o -
+
+
+// SUBTARGET-LABEL:  enum class MyTargetHwModeBits : unsigned {
+// SUBTARGET-NEXT:    DefaultMode = 0,
+// SUBTARGET-NEXT:    XPtr64 = (1 << 0),
+// SUBTARGET-NEXT:    YPtr32 = (1 << 1),
+// SUBTARGET-NEXT:    YPtr64 = (1 << 2),
+// SUBTARGET-EMPTY:
+// SUBTARGET-NEXT:    LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/YPtr64),
+// SUBTARGET-NEXT:  };
+// SUBTARGET-NEXT:  unsigned getHwModeSet() const override;
+// SUBTARGET-NEXT:  unsigned getHwMode(enum HwModeType type = HwMode_Default) const override;
+
+// SUBTARGET-LABEL: unsigned MyTargetGenSubtargetInfo::getHwModeSet() const {
+// SUBTARGET{LITERAL}:[[maybe_unused]] const auto *Subtarget =
+// SUBTARGET-NEXT:        static_cast<const MyTargetSubtarget *>(this);
+// SUBTARGET-NEXT:  // Collect HwModes and store them as a bit set.
+// SUBTARGET-NEXT:  unsigned Modes = 0;
+// SUBTARGET-NEXT:  if ((Subtarget->is64Bit()) && (!Subtarget->useYRegForPtr())) Modes |= (1 << 0);
+// SUBTARGET-NEXT:  if ((!Subtarget->is64Bit()) && (Subtarget->useYRegForPtr())) Modes |= (1 << 1);
+// SUBTARGET-NEXT:  if ((Subtarget->is64Bit()) && (Subtarget->useYRegForPtr())) Modes |= (1 << 2);
+// SUBTARGET-NEXT:  return Modes;
+// SUBTARGET-NEXT:}
+
+// INSTRINFO-LABEL: extern const MyTargetInstrTable MyTargetDescs
+// INSTRINFO:      { MyTarget::EvenPtrRC, 0|(1<<MCOI::LookupRegClassByHwMode), MCOI::OPERAND_REGISTER, 0 },
+// INSTRINFO-NEXT: { MyTarget::EvenXRegsRegClassID, 0, MCOI::OPERAND_REGISTER, 0 },
+// INSTRINFO-NEXT: { MyTarget::PtrRC, 0|(1<<MCOI::LookupRegClassByHwMode), MCOI::OPERAND_REGISTER, 0 }, { MyTarget::PtrRC, 0|(1<<MCOI::LookupRegClassByHwMode), MCOI::OPERAND_REGISTER, 0 },
+// INSTRINFO-NEXT: { MyTarget::XRegsRegClassID, 0, MCOI::OPERAND_REGISTER, 0 }, { MyTarget::XRegsRegClassID, 0, MCOI::OPERAND_REGISTER, 0 },
+// INSTRINFO-NEXT: { MyTarget::YRegsRegClassID, 0, MCOI::OPERAND_REGISTER, 0 }, { MyTarget::YRegsRegClassID, 0, MCOI::OPERAND_REGISTER, 0 },
+
+// INSTRINFO-LABEL: extern const int16_t MyTargetRegClassByHwModeTables[4][2] = {
+// INSTRINFO-NEXT:   { // DefaultMode
+// INSTRINFO-NEXT:     MyTarget::EvenXRegsRegClassID,
+// INSTRINFO-NEXT:     MyTarget::XRegsRegClassID,
+// INSTRINFO-NEXT:   },
+// INSTRINFO-NEXT:   { // XPtr64
+// INSTRINFO-NEXT:     MyTarget::EvenXRegsRegClassID,
+// INSTRINFO-NEXT:     MyTarget::XRegsRegClassID,
+// INSTRINFO-NEXT:   },
+// INSTRINFO-NEXT:   { // YPtr32
+// INSTRINFO-NEXT:     MyTarget::EvenYRegsRegClassID,
+// INSTRINFO-NEXT:     MyTarget::YRegsRegClassID,
+// INSTRINFO-NEXT:   },
+// INSTRINFO-NEXT:   { // YPtr64
+// INSTRINFO-NEXT:     MyTarget::EvenYRegsRegClassID,
+// INSTRINFO-NEXT:     MyTarget::YRegsRegClassID,
+// INSTRINFO-NEXT:   },
+// INSTRINFO-NEXT: };
+
+// ASMWRITER-LABEL: static const AliasPatternCond Conds[] = {
+// ASMWRITER-NEXT:    // (TEST_PTRREG PtrRC:$dst, PtrRC:$src) - 0
+// ASMWRITER-NEXT:    {AliasPatternCond::K_RegClassByHwMode, MyTarget::PtrRC},
+// ASMWRITER-NEXT:    {AliasPatternCond::K_RegClassByHwMode, MyTarget::PtrRC},
+// ASMWRITER-NEXT:    // (TEST_PTRREG EvenPtrRC:$dst, EvenPtrRC:$src) - 2
+// ASMWRITER-NEXT:    {AliasPatternCond::K_RegClassByHwMode, MyTarget::EvenPtrRC},
+// ASMWRITER-NEXT:    {AliasPatternCond::K_RegClassByHwMode, MyTarget::EvenPtrRC},
+// ASMWRITER-NEXT:    // (TEST_PTRREG NullReg, PtrRC:$src) - 4
+// ASMWRITER-NEXT:    {AliasPatternCond::K_Custom, 1/*NullReg*/},
+// ASMWRITER-NEXT:    {AliasPatternCond::K_RegClassByHwMode, MyTarget::PtrRC},
+// ASMWRITER-NEXT:    // (TEST_PTRREG NullReg, EvenPtrRC:$src) - 6
+// ASMWRITER-NEXT:    {AliasPatternCond::K_Custom, 1/*NullReg*/},
+// ASMWRITER-NEXT:    {AliasPatternCond::K_RegClassByHwMode, MyTarget::EvenPtrRC},
+// ASMWRITER-NEXT:    // (TEST_XREG X0, XRegs:$src) - 8
+// ASMWRITER-NEXT:    {AliasPatternCond::K_Reg, MyTarget::X0},
+// ASMWRITER-NEXT:    {AliasPatternCond::K_RegClass, MyTarget::XRegsRegClassID},
+// ASMWRITER-NEXT:    // (TEST_XREG X0, EvenXRegs:$src) - 10
+// ASMWRITER-NEXT:    {AliasPatternCond::K_Reg, MyTarget::X0},
+// ASMWRITER-NEXT:    {AliasPatternCond::K_RegClass, MyTarget::EvenXRegsRegClassID},
+// ASMWRITER-NEXT:    // (TEST_XREG ModeCountReg, XRegs:$src) - 12
+// ASMWRITER-NEXT:    {AliasPatternCond::K_Custom, 2/*ModeCountReg*/},
+// ASMWRITER-NEXT:    {AliasPatternCond::K_RegClass, MyTarget::XRegsRegClassID},
+// ASMWRITER-NEXT:    // (TEST_YREG Y0, YRegs:$src) - 14
+// ASMWRITER-NEXT:    {AliasPatternCond::K_Reg, MyTarget::Y0},
+// ASMWRITER-NEXT:    {AliasPatternCond::K_RegClass, MyTarget::YRegsRegClassID},
+// ASMWRITER-NEXT:    // (TEST_YREG Y0, EvenYRegs:$src) - 16
+// ASMWRITER-NEXT:    {AliasPatternCond::K_Reg, MyTarget::Y0},
+// ASMWRITER-NEXT:    {AliasPatternCond::K_RegClass, MyTarget::EvenYRegsRegClassID},
+// ASMWRITER-NEXT:  };
+
+// ASMWRITER-LABEL: static bool MyTargetInstPrinterValidateMCOperand(const MCOperand &MCOp,
+// ASMWRITER-NEXT:                    const MCSubtargetInfo &STI,
+// ASMWRITER-NEXT:                    unsigned PredicateIndex) {
+// ASMWRITER-NEXT:    switch (PredicateIndex) {
+// ASMWRITER-NEXT:    default:
+// ASMWRITER-NEXT:      llvm_unreachable("Unknown MCOperandPredicate kind");
+// ASMWRITER-NEXT:      break;
+// ASMWRITER-NEXT:    case 1: {
...
[truncated]

// CHECK-NEXT: default: return false;
// CHECK-NEXT: case MyTarget::PTR_MOV: {
// CHECK-NEXT: if (MI.getOperand(1).isReg() &&
// CHECK-NEXT: (MI.getOperand(1).getReg() == [](unsigned HwMode) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this usually get inlined? Just curious if we end up with a bunch of these lambdas in the final binary if there are multiple instructions using the same RegClassByHwMode.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we do end up with quite a few, In my current RVY branch there are 25 definitions. I was hoping to put it somewhere central, but not sure where the best place is to be sure that it's accessible from all callsites.

$ grep -rn "\[\](unsigned HwMode)" *
RISCVGenAsmMatcher.inc:2570:      Inst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenAsmMatcher.inc:2581:      Inst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenAsmWriter.inc:37169:    auto getNullReg = [](unsigned HwMode) {
RISCVGenAsmWriter.inc:37221:    auto getReturnReg = [](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:853:        (MI.getOperand(0).getReg() == [](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:872:        (MI.getOperand(0).getReg() == [](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:894:        (MI.getOperand(0).getReg() == [](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:916:        (MI.getOperand(0).getReg() == [](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:1730:      OutInst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:1764:      OutInst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:3640:      OutInst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:3664:      OutInst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:3688:      OutInst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:3714:      OutInst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:5490:        (MI.getOperand(0).getReg() == [](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:5508:        (MI.getOperand(0).getReg() == [](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:5528:        (MI.getOperand(0).getReg() == [](unsigned HwMode) {
RISCVGenCompressInstEmitter.inc:5547:        (MI.getOperand(0).getReg() == [](unsigned HwMode) {
RISCVGenMCPseudoLowering.inc:52:    Inst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenMCPseudoLowering.inc:86:    Inst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenMCPseudoLowering.inc:170:    Inst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenMCPseudoLowering.inc:180:    Inst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenMCPseudoLowering.inc:285:    Inst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenMCPseudoLowering.inc:305:    Inst.addOperand(MCOperand::createReg([](unsigned HwMode) {
RISCVGenMCPseudoLowering.inc:325:    Inst.addOperand(MCOperand::createReg([](unsigned HwMode) {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Emitting one function now, so now we get something like llvm::TARGET::RegisterByHwMode::getFooReg.

// CHECK-NEXT: case MyTarget::PTR_MOV: {
// CHECK-NEXT: if (MI.getOperand(1).isReg() &&
// CHECK-NEXT: (MI.getOperand(1).getReg() == [](unsigned HwMode) {
// CHECK-NEXT: switch (HwMode)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the class isn't specified for one of the modes? Should case 0: be default:?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, let me add another test cases where we have a RegisterByHwMode that is not specified for all HwModes. Since we only have two here it's fully covered.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test case where not all hwmodes are covered.

Copy link
Contributor

@arsenm arsenm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm interested in this for AMDGPU to fix problems with wavesize management

/// where the operand type is a RegClassByHwMode.
class RegisterByHwMode<RegisterClassLike RegClass, list<HwMode> Modes,
list<Register> Registers>
: HwModeSelect<Modes, !size(Registers)>, RegisterOperand<RegClass> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does it need RegisterClassLike?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that the type of the register can be a RegClassByHwMode instead of just a fixed RegisterClass. I need this in the RVY changes since I need to use a PtrRC that is either the normal GPR register class (RVI mode) or the extended YGPR class (RVY mode).

// CHECK-NEXT: default: llvm_unreachable("Unhandled HwMode for Register NullReg");
// CHECK-NEXT: }
// CHECK-NEXT: };
// CHECK-NEXT: return MCOp.isReg() && MCOp.getReg() == getNullReg(STI.getHwMode(MCSubtargetInfo::HwMode_RegInfo));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that getHwMode is a virtual function. It may make sense to cache its result somewhere in the base class.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely agreed, although so far these calls are probably not in a particularly hot path. We can always do something like #174471 once needed.

@s-barannikov
Copy link
Contributor

I'm interested in this for AMDGPU to fix problems with wavesize management

Can you describe what the issue is?

@arsenm
Copy link
Contributor

arsenm commented Jan 9, 2026

I'm interested in this for AMDGPU to fix problems with wavesize management

Can you describe what the issue is?

We have VCC = [VCC_LO, VCC_HI] and the instructions unconditionally use VCC. Depending on the HwMode, VCC should be swapped to the subregister VCC_LO in the instruction definitions

arichardson added a commit to arichardson/upstream-llvm-project that referenced this pull request Jan 16, 2026
This is useful for `InstAlias` where a fixed register may depend on the
HwMode. The motivating use case for this is the RISC-V RVY ISA where
certain instructions mnemonics are remapped to take a different
register class depending on the HwMode and can be used as follows:
```
def NullReg : RegisterByHwMode<PtrRC, [RV32I, RV64I, RV64Y, RV64Y],
                                      [X0,    X0,    X0_Y,  X0_Y]>;
```

Pull Request: llvm#175227
@github-actions
Copy link

github-actions bot commented Jan 20, 2026

✅ With the latest revision this PR passed the C/C++ code formatter.

Created using spr 1.3.8-beta.1
@github-actions
Copy link

github-actions bot commented Jan 20, 2026

🐧 Linux x64 Test Results

  • 188684 tests passed
  • 5009 tests skipped

✅ The build succeeded and all tests passed.

@github-actions
Copy link

github-actions bot commented Jan 20, 2026

🪟 Windows x64 Test Results

  • 129647 tests passed
  • 2876 tests skipped

✅ The build succeeded and all tests passed.

@arsenm
Copy link
Contributor

arsenm commented Jan 20, 2026

lgtm but the tests are still failing

Created using spr 1.3.8-beta.1
@arichardson
Copy link
Member Author

lgtm but the tests are still failing

Sorry pushed too early. Fixed now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llvm:SelectionDAG SelectionDAGISel as well tablegen

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants