LLVM 寄存器信息描述

MCRegister

MCRegister表示一个low level的寄存器,通常作为MCInst中的一个操作数。以我目前的理解,MCRegister对应一个物理寄存器。

实现上,表示寄存器只需要一个编号即可,所以MCRegister的数据成员就一个unsigned。编号与物理寄存器的对应关系是由TableGen决定的。

using MCPhysReg = uint16_t; 

class MCRegister {
    unsigned Reg; // 寄存器编号,取值有如下的特殊含义
    // 0 不是一个寄存器
    // [1, 2^30) 表示物理寄存器
    // [2^30, 2^31)表示stack slot
    // [2^31, 2^32)表示虚拟寄存器
}

MCRegisterClass

MCRegisterClass表示一个寄存器类,是具有某种共同特征或者作用于某个共同场合的寄存器集合,比如具有64位位宽的寄存器可以归为一类:

// GR64 Register Class...
const MCPhysReg GR64[] = {
  X86::RAX, X86::RCX, X86::RDX, X86::RSI, X86::RDI, X86::R8, X86::R9, X86::R10, X86::R11, X86::RBX, X86::R14, X86::R15, X86::R12, X86::R13, X86::RBP, X86::RSP, X86::RIP, 
};

MCRegisterClass的数据成员如下:

class MCRegisterClass {
public:
  using iterator = const MCPhysReg*;
  using const_iterator = const MCPhysReg*;

  const iterator RegsBegin;    // 指向类的成员数组,如GR64
  const uint8_t *const RegSet; // 包含的寄存器的位图,如GR64Bits
  const uint32_t NameIdx;      // 寄存器类名字在RegClassStrings数组的索引,如GR64对应57
  const uint16_t RegsSize;     // 该寄存器类包含的寄存器个数,如GR64中是17个
  const uint16_t RegSetSize;   // 包含寄存器位图的大小,如sizeof(GR64Bits)
  const uint16_t ID;           // 该寄存器类的ID,如X86::GR64RegClassID
  const uint16_t RegSizeInBits;// 该类中寄存器的比特数,如64
  const int8_t CopyCost;       // 在该寄存器类中寄存器复制的开销,如1
  const bool Allocatable;      // 其中的寄存器是否可分配,true or false
  ...
}

这些成员的含义已在注释中说明,我们以之前列举的GR64 MCRegisterClass为例进行解释,该寄存器类的定义在X86MCRegisterClasses[X86::GR64RegClassID]中,内容如下:

{ GR64, GR64Bits, 57, 17, sizeof(GR64Bits), X86::GR64RegClassID, 64, 1, true },
  1. RegsBegin指向该寄存器类的成员数组,此例中就是前面列举的const MCPhysReg GR64[]数组。RegsSize描述这个数组的大小,此例是17。

  2. RegSet是一个位图,位图中第N位是1表示编号是N的物理寄存器存在;RegSetSize是该位图的大小,因为我们并不需要建立所有物理寄存器的位图,只需要能表示该RegClass中最大编号的物理寄存器即可。

  3. NameIdx是该寄存器类的名字,只不过存的是名字在字符数组中的下标,可以在X86RegClassStrings中验证:

    extern const char X86RegClassStrings[] = {
        ...
        /* 57 */ "GR64\0" //第57字节开始表示GR64的名字
    }
    
  4. ID表示寄存器类的标识;RegSizeInBits表示其中的寄存器大小,显然GR64都是64位的寄存器;CopyCost表示GR64中寄存器互相复制的开销,这里显示只有1,表示开销很小;Allocatable表示该寄存器类是否可以用于寄存器分配,显然GR64都是可分配物理寄存器。(对应的控制寄存器是不可分配的)。

MCRegisterDesc

MCRegisterDesc描述寄存器信息的核心类。基本上一个寄存器的子寄存器(SubRegs)、超寄存器(SuperRegs)都由它描述。如果某个寄存器A的位域被另一个寄存器B完全覆盖,则可以称A是B的子寄存器或者说B是A的超寄存器。如X86中AH,ALAX的子寄存器,EAX,RAXAX的超寄存器。

MCRegisterDesc类在实现上是一个POD。数据成员如下:

struct MCRegisterDesc {
  uint32_t Name;      // Printable name for the reg (for debugging)
  uint32_t SubRegs;   // Sub-register set, described above
  uint32_t SuperRegs; // Super-register set, described above

  // Offset into MCRI::SubRegIndices of a list of sub-register indices for each
  // sub-register in SubRegs.
  uint32_t SubRegIndices;

  // RegUnits - Points to the list of register units. The low 4 bits holds the
  // Scale, the high bits hold an offset into DiffLists. See MCRegUnitIterator.
  uint32_t RegUnits;

  /// Index into list with lane mask sequences. The sequence contains a lanemask
  /// for every register unit.
  uint16_t RegUnitLaneMasks;
};

我们以RAX这一寄存器为例来分析这些数据成员的含义。首先看一下有关RAXMCRegisterDesc,其在const MCRegisterDesc X86RegDesc[X86::RAX]中的定义如下:

{ 1229, 235, 2, 6, 1636, 8 }

MCRegisterInfo

MCRegisterInfo是整个MCRegister相关的接口类。它是对以上几个类的封装,所有对物理寄存器的访问都应该通过MCRegisterInfo的接口来使用。

  1. MCRegisterInfo应该能得到任意物理寄存器的信息。所以内部有一个MCRegisterDesc的数组:

    const MCRegisterDesc *Desc; // Pointer to the descriptor array
    unsigned NumRegs;           // Number of entries in the array
    

    该数组在X86下是通过InitX86MCRegisterInfo函数初始化的,两成员分别被赋值X86RegDesc与292。获取某个物理寄存器信息时,直接使用operator []get函数即可:

    const MCRegisterDesc &operator[](MCRegister RegNo) const {
      assert(RegNo < NumRegs &&
             "Attempting to access record for invalid register number!");
      return Desc[RegNo];
    }
       
    /// Provide a get method, equivalent to [], but more useful with a
    /// pointer to this object.
    const MCRegisterDesc &get(MCRegister RegNo) const {
      return operator[](RegNo);
    }
    

    此外,MCRegisterInfo提供了每个物理寄存器的名字数组,可以用getName方法获取名字:

    const char *RegStrings;     // Pointer to the string table.
       
    /// Return the human-readable symbolic target-specific name for the
    /// specified physical register.
    const char *getName(MCRegister RegNo) const {
      return RegStrings + get(RegNo).Name;
    }
    

    在所有物理寄存器中,有两个特殊的寄存器被单独列举出来。一个是返回地址寄存器,另一个是程序计数寄存器:

    MCRegister RAReg; // Return address register
    MCRegister PCReg; // Program counter register
    

    可直接调用对应的get方法进行获取。

  2. MCRegisterInfo提供了对寄存器类的访问,在实现上所有寄存器类被保存在一个数组中。MCRegisterInfo提供了一个数组地址与大小来引用该数组:

    const MCRegisterClass *Classes; // Pointer to the regclass array
    unsigned NumClasses;            // Number of entries in the array
    const char *RegClassStrings;    // Pointer to the class strings.
    

    这两成员在X86下被初始化为X86MCRegisterClasses与126。表明X86下共有126个寄存器类。

    同样地,寄存器类也有对应的名字数组与get方法:

    const char *RegClassStrings;  // Pointer to the class strings.
       
    const char *getRegClassName(const MCRegisterClass *Class) const {
      return RegClassStrings + Class->NameIdx;
    }
    
  3. 提供获取物理寄存器所包含的RegUnit的方法。回忆一下RegUnit是最小不可划分的子寄存器。它的值被编码在DiffLists差分表中,每个RegUnit需要再索引RegUnitRoots才能得到寄存器编号。

    const MCPhysReg *DiffLists;  // Pointer to the difflists array
    const MCPhysReg (*RegUnitRoots)[2]; // Pointer to regunit root table.
    unsigned NumRegUnits;        // Number of regunits. also length of RegUnitRoots.
    

    以上数据成员分别被初始化为X86RegDiffListsX86RegUnitRoots,173。

    要遍历一个物理寄存器所包含的RegUnit,LLVM提供了MCRegUnitIteratorMCRegUntiRootIterator跌代器供使用,典型的方式如下:

    for (MCRegUnitIterator RUI(RegNo, MCRI); RUI.isValid(); ++RUI) {
        for (MCRegUnitRootIterator RURI(*RUI, MCRI); RURI.isValid(); ++RURI) {
            visit(*RURI); // *RURI即是regunit的物理寄存器
        }
    }
    

    此外,MCRegisterInfo中有一个RegUnitMaskSequnce指向RegUnitLaneBitmask。获取一个物理寄存器的LaneBitmask也需要通过MCRegUnitMaskIterator迭代器遍历。

  4. MCRegisterInfo也封装了对一个物理寄存器的SubRegIndices的访问。比如我们想得到某个子寄存器的起始比特与大小,需要先获得该SubRegIndex。与此相关的两个数据成员是SubRegIndices数组与SubRegIdxRanges数组:

    const uint16_t *SubRegIndices; // Pointer to the subreg lookup array.
    unsigned NumSubRegIndices; // Number of subreg indices.
    const SubRegCoveredBits *SubRegIdxRanges; // Pointer to the subreg covered bit ranges array.
    

    SubRegIndices是一个索引表,布局没有特殊安排,由TableGen自动生成,被初始化为X86SubRegIdxListsSubRegIdxRanges是每一个子寄存器的起止范围,被赋值为X86SubRegIdxRanges

    访问它们同样需要使用迭代器,这里用的是MCSubRegIndexIterator。典型用法如下:

    for (MCSubRegIndexIterator SRII(RegNo, MCRI); SRII.isValid(); ++SRII) {
        visit(SRII.getSubRegIndex, SRII.getSubReg...); // visit是你自己的函数
    }
    
    • 最后是和Dwarf等进行寄存器映射的结构,暂时不准备看它的实现。
    unsigned L2DwarfRegsSize;
    unsigned EHL2DwarfRegsSize;
    unsigned Dwarf2LRegsSize;
    unsigned EHDwarf2LRegsSize;
    const DwarfLLVMRegPair *L2DwarfRegs;  // LLVM to Dwarf regs mapping
    const DwarfLLVMRegPair *EHL2DwarfRegs;// LLVM to Dwarf regs mapping EH
    const DwarfLLVMRegPair *Dwarf2LRegs;  // Dwarf to LLVM regs mapping
    const DwarfLLVMRegPair *EHDwarf2LRegs;// Dwarf to LLVM regs mapping EH
    DenseMap<MCRegister, int> L2SEHRegs;  // LLVM to SEH regs mapping
    DenseMap<MCRegister, int> L2CVRegs;   // LLVM to CV regs mapping
    

TargetRegisterClass

前面涉及的类都位于MC层,该层比较low-level,关注硬件相关的属性。而TargetRegisterXXX相关的寄存器类则与CodeGen相关,与ABI、寄存器分配算法等联系精密。所以理解这些类需要对LLVM后端代码生成比较熟悉了,下面将讲解一些实现相关的细节,有关CodeGen接口设计部分留待后续。

TargetRegisterClass是一个所有成员都是public的类,X86架构下每个寄存器类都有唯一对应的TargetRegisterClass。该类的数据成员如下:

class TargetRegisterClass {
public:
  using iterator = const MCPhysReg *;
  using const_iterator = const MCPhysReg *;
  using sc_iterator = const TargetRegisterClass* const *;

  // Instance variables filled by tablegen, do not use!
  const MCRegisterClass *MC;
  const uint32_t *SubClassMask;
  const uint16_t *SuperRegIndices;
  const LaneBitmask LaneMask;
  /// Classes with a higher priority value are assigned first by register
  /// allocators using a greedy heuristic. The value is in the range [0,63].
  const uint8_t AllocationPriority;
  /// Whether the class supports two (or more) disjunct subregister indices.
  const bool HasDisjunctSubRegs;
  /// Whether a combination of subregisters can cover every register in the
  /// class. See also the CoveredBySubRegs description in Target.td.
  const bool CoveredBySubRegs;
  const sc_iterator SuperClasses;
  ArrayRef<MCPhysReg> (*OrderFunc)(const MachineFunction&);
  ...
}

这个类没有构造函数,所有成员都是TableGen显示初始化完成。以GR8RegClass为例对该类进行分析,其定义如下:

extern const TargetRegisterClass GR8RegClass = {
  &X86MCRegisterClasses[GR8RegClassID],
  GR8SubClassMask,
  SuperRegIdxSeqs + 2,
  LaneBitmask(0x0000000000000001),
  0,
  false, /* HasDisjunctSubRegs */
  false, /* CoveredBySubRegs */
  NullRegClasses,
  GR8GetRawAllocationOrder
};

TargetRegisterInfo

X86TargetRegisterInfo

注解

  1. 不同LLVM版本某个架构下寄存器的编号有可能不同,比如本文中使用的RAX,LLVM-17中其值是51,但是在LLVM-15中是49,实际使用时应该用枚举常量。