Issues with implementing GlobalISel on DSP chips. [GISel/InstructionSelect] Assertion: Register class not set, wrong accessor?

Preface

My question is similar to this one, but it is also different.
2022: [GISel/InstructionSelect] Assertion: Register class not set, wrong accessor?

A brief introduction to my compiler backend information

Before I describe my problem, please allow me to describe the hardware situation of the compiler backend I have implemented.

There are two register classes in the hardware, SGPR and SRAR. SGPR includes registers: R1, R2, R3. The SRAR register class includes AR1, AR2, and AR3, as well as OR1, OR2, and OR3. SGPR registers are used for storing integers and floating-point numbers. SRAR is used for storing addresses, where AR stores the base address and OR stores the offset.

If there is a need to transfer data between registers of the SGPR and SAR, the SMVA instruction needs to be used.

For example:

...
// SVMI: immediate move instruction
// SMVA: address move instruction
// SST: scalar store instruction
SMVI 100, R3; // R3 = 100;
SMVI 1, R1;     // R1 is assigned the value 1
SMVA R1, AR1; // Give the value of R1 to AR1 as the base address
SMVI 8, R2; 
SMVA R2, OR1;
SST R3, AR1, OR1; // Save the value of the R3 register at the address of AR1+OR1
...

I now have the following tablegen definition:

def SGP32Regs : RegisterClass<"Matrix", [i32], 32, (add (sequence "R%u", 0, 63))>;
def SGP64Regs : RegisterClass<"Matrix", [i64], 64, (add (sequence "R%u", 0, 63))>;
...
/// Scalar General Purpose Registers: 
def SGPRRegBank : RegisterBank<"SGPR", 
    [SGP32Regs, SGP64Regs, SGPv2i32Regs, SGPv4i16Regs]>;

def ARegs : RegisterClass<"Matrix", [i64], 64, (add (sequence "AR%u", 0, 15))>; 
...
/// Scalar Address Base Address Registers:
def SARRegBank : RegisterBank<"SAR",
    [SARegs]>;

Problems encountered in global instruction selection

gMIR

...
  %15:sgpr(s64) = G_LOAD %27:sgpr(p0) :: (dereferenceable load (s64) from %ir.i)
  %25:sgpr(s64) = G_CONSTANT i64 3
  %16:sgpr(s64) = G_SHL %15:sgpr, %25:sgpr(s64)
  %29:sgpr(p0) = G_FRAME_INDEX %stack.1.b
  %17:sgpr(p0) = G_PTR_ADD %29:sgpr, %16:sgpr(s64)
  G_STORE %14:sgpr(s64), %17:sgpr(p0) :: (store (s64) into %ir.arrayidx1)
...

Global Instruction Selection procedure:

Selecting: 
  G_STORE %14:sgpr(s64), %17:sgpr(p0) :: (store (s64) into %ir.arrayidx1)
5: GIM_SwitchOpcode(MIs[0], [45, 201), Default=14437, JumpTable...) // Got=84
2936: Begin try-block
2941: GIM_CheckType(MIs[0]->getOperand(0), TypeID=1)
2942: Begin try-block
2947: GIM_CheckMemorySizeEqual(MIs[0]->memoperands() + 0, Size=8)
8 bytes vs 8 bytes
2950: GIM_CheckAtomicOrderingWeakerThan(MIs[0], 5)
2953: GIM_CheckAtomicOrderingOrStrongerThan(MIs[0], 1)
2953: Rejected
2990: Resume at 2990 (2 try-blocks remain)
2991: Begin try-block
2996: GIM_CheckMemorySizeEqualToLLT(MIs[0]->memoperands() + 0, OpIdx=0)
2999: GIM_CheckAtomicOrdering(MIs[0], 0)
3003: GIM_CheckRegBankForClass(MIs[0]->getOperand(0), RCEnum=5)
3007: GIM_CheckPointerToAny(MIs[0]->getOperand(1), SizeInBits=0)
3008: Begin try-block
3014: State.Renderers[0] = GIM_CheckComplexPattern(MIs[0]->getOperand(1), ComplexPredicateID=5)
3017: GIR_BuildMI(OutMIs[0], 494)
3021: GIR_Copy(OutMIs[0], MIs[0], 0)
3025: GIR_ComplexSubOperandRenderer(OutMIs[0], 0, 0)
3029: GIR_ComplexSubOperandRenderer(OutMIs[0], 0, 1)
3031: GIR_MergeMemOperands(OutMIs[0], MIs[0])
3035: GIR_EraseFromParent(MIs[0])
Converting operand: %14:sgpr
Converting operand: %29:sgpr
Converting operand: %16:sgpr
3037: GIR_ConstrainSelectedInstOperands(OutMIs[0])
3038: GIR_Done
Into:
  %35:saregs = COPY %29:sgpr(p0)
  %36:soregs = COPY %16:sgpr(s64)
  SSTW %14:sgpregs(s64), %35:saregs, %36:soregs :: (store (s64) into %ir.arrayidx1)

The problem arises

The COPY instruction was added automatically, which would have been fine.However, during the verification process after instruction selection, there are issues that arise.

Function call stack

The problems that arise are:

llc: /root/project/llvm-15.0.1-for-matrix/llvm/include/llvm/CodeGen/MachineRegisterInfo.h:644: const llvm::TargetRegisterClass *llvm::MachineRegisterInfo::getRegClass(llvm::Register) const: Assertion `VRegInfo[Reg.id()].first.is<const TargetRegisterClass *>() && "Register class not set, wrong accessor"' failed.

In other words, the RegClass of the variable %29 in the instruction %35:saregs = COPY %29:sgpr(p0) has not been set correctly.

I’m confused and puzzled, I need help!

%35:saregs = COPY %29:sgpr(p0)

This instruction is automatically generated during the instruction selection process, and I am not sure why a COPY instruction is being generated, and why the register class for the variable %29 is not set correctly.

gMIR at the time of the error occurrence.

...
  %16:sgpregs(s64) = SSHFLLrr %25:sgpregs(s64), %15:sgpregs(s64), 0
  %37:sraregs = SADDAri %stack.1.b, 0
  %29:sgpr(p0) = COPY %37:sraregs
  %35:saregs = COPY %29:sgpr(p0)
  %36:soregs = COPY %16:sgpregs(s64)
  SSTW %14:sgpregs(s64), %35:saregs, %36:soregs :: (store (s64) into %ir.arrayidx1)
...

error info:
llc: /root/project/llvm-15.0.1-for-matrix/llvm/include/llvm/CodeGen/MachineRegisterInfo.h:644: const llvm::TargetRegisterClass *llvm::MachineRegisterInfo::getRegClass(llvm::Register) const: Assertion `VRegInfo[Reg.id()].first.is<const TargetRegisterClass *>() && “Register class not set, wrong accessor”’ failed.

I would like to know how to avoid this error. I am currently clueless and would greatly appreciate it if you could provide me with some ideas, suggestions, advice, guidance, or relevant resources.

1 Like

I think when %29:sgpr(p0) = COPY %37:sraregs goes through instruction selection, you need to constrain the destination register to a register class. See RISCVInstructionSelector::selectCopy for example.

Instruction selection proceeds backwards through the function so instructions are selected before their inputs. When a target specific instruction like SSTW is selected, the register class for the destination and source registers will be constrained. Since COPY is a generic instruction that doesn’t have a specific register class assignment, this doesn’t happen. If a COPY is used by a target specific instruction, the selection of that instruction will constrain the register class as a source. If a COPY is used by another COPY, this doesn’t happen. So the destination register class of the COPY needs to be constrained if it hasn’t been already.

Hopefully that helps. I’m not sure if I explained that well. Feel free to ask questions.

Thank you very much for your help and suggestions.

Restatement of problem

The selectCopy() function you mentioned is called during the instruction selection phase. Before the function is called, LLVM has already saved the instruction MII to MI. Then MII points to its previous instruction.

I use “ComplexPattern+instruction Pat” with TableGen for processing.

def ro_Windexed64 : ComplexPattern<iPTR, 2, "SelectAddrModeWRO<64>", []>;

def : Pat<(store SGP64Regs:$val, (ro_Windexed64 SARegs:$ptr, SGP64Regs:$offset)),
          (SSTW SGP64Regs:$val, SARegs:$ptr, SORegs:$offset)>;

These three COPY instructions in between are automatically generated and cannot re-enter the instruction selection phase. This is because the MII variable always points to the next instruction to be processed, and the automatically generated COPY instructions are not pointed to by the MII variable. I wanted to handle them through the selectCopy function and set their RegClass, but it’s not possible.

LLVM Sources Code

Then, during the “find redundant copies” phase, an error occurred because it was necessary to obtain the RegClass of the operand.

%35:saregs = COPY %29:sgpr(p0)

error info:

llc: /root/project/llvm-15.0.1-for-matrix/llvm/include/llvm/CodeGen/MachineRegisterInfo.h:644: const llvm::TargetRegisterClass *llvm::MachineRegisterInfo::getRegClass(llvm::Register) const: Assertion `VRegInfo[Reg.id()].first.is<const TargetRegisterClass *>() && “Register class not set, wrong accessor”’ failed.

find redundant copies:

  for (MachineBasicBlock *MBB : post_order(&MF)) {
    ISel->CurMBB = MBB;
    SelectedBlocks.insert(MBB);
    if (MBB->empty())
      continue;

    // Select instructions in reverse block order. We permit erasing so have
    // to resort to manually iterating and recognizing the begin (rend) case.
    bool ReachedBegin = false;
    for (auto MII = std::prev(MBB->end()), Begin = MBB->begin();
         !ReachedBegin;) {
#ifndef NDEBUG
      // Keep track of the insertion range for debug printing.
      const auto AfterIt = std::next(MII);
#endif
      // Select this instruction.
      MachineInstr &MI = *MII;

      // And have our iterator point to the next instruction, if there is one.
      if (MII == Begin)
        ReachedBegin = true;
      else
        --MII;

      LLVM_DEBUG(dbgs() << "Selecting: \n  " << MI);

      // We could have folded this instruction away already, making it dead.
      // If so, erase it.
      if (isTriviallyDead(MI, MRI)) {
        LLVM_DEBUG(dbgs() << "Is dead; erasing.\n");
        MI.eraseFromParent();
        continue;
      }

      // Eliminate hints.
      if (isPreISelGenericOptimizationHint(MI.getOpcode())) {
        Register DstReg = MI.getOperand(0).getReg();
        Register SrcReg = MI.getOperand(1).getReg();

        // At this point, the destination register class of the hint may have
        // been decided.
        //
        // Propagate that through to the source register.
        const TargetRegisterClass *DstRC = MRI.getRegClassOrNull(DstReg);
        if (DstRC)
          MRI.setRegClass(SrcReg, DstRC);
        assert(canReplaceReg(DstReg, SrcReg, MRI) &&
               "Must be able to replace dst with src!");
        MI.eraseFromParent();
        MRI.replaceRegWith(DstReg, SrcReg);
        continue;
      }

      if (!ISel->select(MI)) {
        // FIXME: It would be nice to dump all inserted instructions.  It's
        // not obvious how, esp. considering select() can insert after MI.
        reportGISelFailure(MF, TPC, MORE, "gisel-select", "cannot select", MI);
        return false;
      }

      // Dump the range of instructions that MI expanded into.
      LLVM_DEBUG({
        auto InsertedBegin = ReachedBegin ? MBB->begin() : std::next(MII);
        dbgs() << "Into:\n";
        for (auto &InsertedMI : make_range(InsertedBegin, AfterIt))
          dbgs() << "  " << InsertedMI;
        dbgs() << '\n';
      });
    }
  }

My current solution is as follows:

My current solution is to no longer use the “Pat+ComplexPattern” approach for instruction selection. Instead, I handle it within the select function in the C++ code and manually specify the regclass of the operand. This temporarily solves the problem.

Should G_FRAME_INDEX register bank select to SAR instead of SGPR? It looks like the instruction it becomes is SADDAri which writes an sraregs register.

Sure, thank you very much. I’ll go and give it a try.