LLVM后端入门3:寄存器组和寄存器类

阿里云教程3个月前发布
23 0 0

这里需要描述一个具体的特定于目标的类,它表示目标机器的寄存器文件。这个类被称为XXXRegisterInfo(其中XXX标识目标),并表示用于寄存器分配的类寄存器文件数据。它还描述了寄存器之间的交互。

还需要定义寄存器类来对相关的寄存器进行分类。对于对某条指令都以相同方式处理的寄存器组,应该添加一个寄存器类。典型的例子是用于整型、浮点型或矢量型寄存器的寄存器类。寄存器分配器允许指令使用指定寄存器类中的任何寄存器,以类似的方式执行该指令。寄存器类将虚拟寄存器分配给来自这些集合的指令,并且寄存器类允许独立于目标的寄存器分配器自动选择实际的寄存器。

寄存器的大部分代码,包括寄存器定义、寄存器别名和寄存器类,都是由TableGen从XXXRegisterInfo生成的. td输入文件,并放置在XXXGenRegisterInfo.h.inc和XXXGenRegisterInfo.Inc输出文件。XXXRegisterInfo实现中的一些代码需要手工编码。

定义一个寄存器

XXXRegisterInfo.td 文件通常从目标机器的寄存器定义开始,Register类(在Target.td中指定)用于为每个寄存器定义对象。指定的字符串n成为寄存器的名称,基本寄存器对象没有任何子寄存器,也没有指定任何别名。



// Register - You should define one instance of this class for each register
// in the target machine. String n will become the "name" of the register.
class Register<string n, list<string> altNames = []> {
  string Namespace = "";
  string AsmName = n;
  list<string> AltNames = altNames;
 
  // Aliases - A list of registers that this register overlaps with. A read or
  // modification of this register can potentially read or modify the aliased
  // registers.
  list<Register> Aliases = [];
 
  // SubRegs - A list of registers that are parts of this register. Note these
  // are "immediate" sub-registers and the registers within the list do not
  // themselves overlap. e.g. For X86, EAX's SubRegs list contains only [AX],
  // not [AX, AH, AL].
  list<Register> SubRegs = [];

  // SubRegIndices - For each register in SubRegs, specify the SubRegIndex used
  // to address it. Sub-sub-register indices are automatically inherited from
  // SubRegs.
  list<SubRegIndex> SubRegIndices = [];

  // RegAltNameIndices - The alternate name indices which are valid for this
  // register.
  list<RegAltNameIndex> RegAltNameIndices = [];

  // DwarfNumbers - Numbers used internally by gcc/gdb to identify the register.
  // These values can be determined by locating the <target>.h file in the
  // directory llvmgcc/gcc/config/<target>/ and looking for REGISTER_NAMES. The
  // order of these names correspond to the enumeration used by gcc. A value of
  // -1 indicates that the gcc number is undefined and -2 that register number
  // is invalid for this mode/flavour.
  list<int> DwarfNumbers = [];

  // CostPerUse - Additional cost of instructions using this register compared
  // to other registers in its class. The register allocator will try to
  // minimize the number of instructions using a register with a CostPerUse.
  // This is used by the ARC target, by the ARM Thumb and x86-64 targets, where
  // some registers require larger instruction encodings, by the RISC-V target,
  // where some registers preclude using some C instructions. By making it a
  // list, targets can have multiple cost models associated with each register
  // and can choose one specific cost model per Machine Function by overriding
  // TargetRegisterInfo::getRegisterCostTableIndex. Every target register will
  // finally have an equal number of cost values which is the max of costPerUse
  // values specified. Any mismatch in the cost values for a register will be
  // filled with zeros. Restricted the cost type to uint8_t in the
  // generated table. It will considerably reduce the table size.
  list<int> CostPerUse = [0];

  // CoveredBySubRegs - When this bit is set, the value of this register is
  // completely determined by the value of its sub-registers. For example, the
  // x86 register AX is covered by its sub-registers AL and AH, but EAX is not
  // covered by its sub-register AX.
  bit CoveredBySubRegs = false;

  // HWEncoding - The target specific hardware encoding for this register.
  bits<16> HWEncoding = 0;

  bit isArtificial = false;

  // isConstant - This register always holds a constant value (e.g. the zero
  // register in architectures such as MIPS)
  bit isConstant = false;

  /// PositionOrder - Indicate tablegen to place the newly added register at a later
  /// position to avoid iterations on them on unsupported target.
  int PositionOrder = 0;
}

例如,在
X86RegisterInfo.td
文件中,存在使用
Register
类的寄存器定义,示例如下:


def AL : Register<"AL">, DwarfRegNum<[0, 0, 0]>;

这定义了寄存器
AL
,并为其分配了一些值(通过
DwarfRegNum
)。这些值被 gcc、gdb 或调试信息写入器(debug information writer)用来识别寄存器。对于寄存器
AL

DwarfRegNum
采用一个包含 3 个值的数组,分别代表 3 种不同的模式:第一个元素用于 X86-64 模式,第二个用于 X86-32 模式下的异常处理(EH),第三个是通用模式(generic)。


-1
是一个特殊的 Dwarf 编号,表示该寄存器在 gcc 中的编号是未定义的。


-2
表示该寄存器编号在当前模式下是无效的。

根据
X86RegisterInfo.td
文件中前面描述的那一行,TableGen 工具会在
X86GenRegisterInfo.inc
文件中生成如下代码:



static const unsigned GR8[] = { X86::AL, ... };
 
const unsigned AL_AliasSet[] = { X86::AX, X86::EAX, X86::RAX, 0 };
 
const TargetRegisterDesc RegisterDescriptors[] = {
  ...
{ "AL", "AL", AL_AliasSet, Empty_SubRegsSet, Empty_SubRegsSet, AL_SuperRegsSet }, ...

基于寄存器信息文件,TableGen为每一个寄存器创建一个
TargetRegisterDesc
对象,
TargetRegisterDesc
的定义在llvm/include/llvm/Target/TargetRegisterInfo.h,有如下字段:



struct TargetRegisterDesc {
  const char     *AsmName;      // Assembly language name for the register
  const char     *Name;         // Printable name for the reg (for debugging)
  const unsigned *AliasSet;     // Register Alias Set
  const unsigned *SubRegs;      // Sub-register set
  const unsigned *ImmSubRegs;   // Immediate sub-register set
  const unsigned *SuperRegs;    // Super-register set
};

TableGen会利用完整的目标描述文件(.td 文件),确定寄存器的文本名称(存储在
TargetRegisterDesc

AsmName

Name
字段中),以及其他寄存器与该已定义寄存器的关联关系(存储在
TargetRegisterDesc
的其他字段中)。

在本示例中,其他定义将寄存器 “AX”“EAX” 和 “RAX” 确立为彼此的别名,因此 TableGen 会为这个寄存器别名集生成一个以空字符结尾的数组(
AL_AliasSet
)。


Register
类通常用作更复杂类的基类。在
Target.td
中,
Register
类是
RegisterWithSubRegs
类的基类 ——
RegisterWithSubRegs
类用于定义需要在
SubRegs
列表中指定子寄存器的一类寄存器,如下所示:



// RegisterWithSubRegs - This can be used to define instances of Register which
// need to specify sub-registers.
// List "subregs" specifies which registers are sub-registers to this one. This
// is used to populate the SubRegs and AliasSet fields of TargetRegisterDesc.
// This allows the code generator to be careful not to put two values with
// overlapping live ranges into registers which alias.
class RegisterWithSubRegs<string n, list<Register> subregs> : Register<n> {
  let SubRegs = subregs;
}

TableGen 会利用完整的目标描述文件(.td 文件),确定寄存器的文本名称(存储在
TargetRegisterDesc

AsmName

Name
字段中),以及其他寄存器与该已定义寄存器的关联关系(存储在
TargetRegisterDesc
的其他字段中)。

在本示例中,其他定义将寄存器 “AX”“EAX” 和 “RAX” 确立为彼此的别名,因此 TableGen 会为这个寄存器别名集生成一个以空字符结尾的数组(
AL_AliasSet
)。


Register
类通常用作更复杂类的基类,在
Target.td
中:



// Register - You should define one instance of this class for each register
// in the target machine. String n will become the "name" of the register.
class Register<string n, list<string> altNames = []> {
  string Namespace = "";
  string AsmName = n;
  list<string> AltNames = altNames;
 
  // Aliases - A list of registers that this register overlaps with. A read or
  // modification of this register can potentially read or modify the aliased
  // registers.
  list<Register> Aliases = [];
 
  // SubRegs - A list of registers that are parts of this register. Note these
  // are "immediate" sub-registers and the registers within the list do not
  // themselves overlap. e.g. For X86, EAX's SubRegs list contains only [AX],
  // not [AX, AH, AL].
  list<Register> SubRegs = [];

  // SubRegIndices - For each register in SubRegs, specify the SubRegIndex used
  // to address it. Sub-sub-register indices are automatically inherited from
  // SubRegs.
  list<SubRegIndex> SubRegIndices = [];

  // RegAltNameIndices - The alternate name indices which are valid for this
  // register.
  list<RegAltNameIndex> RegAltNameIndices = [];

  // DwarfNumbers - Numbers used internally by gcc/gdb to identify the register.
  // These values can be determined by locating the <target>.h file in the
  // directory llvmgcc/gcc/config/<target>/ and looking for REGISTER_NAMES. The
  // order of these names correspond to the enumeration used by gcc. A value of
  // -1 indicates that the gcc number is undefined and -2 that register number
  // is invalid for this mode/flavour.
  list<int> DwarfNumbers = [];

  // CostPerUse - Additional cost of instructions using this register compared
  // to other registers in its class. The register allocator will try to
  // minimize the number of instructions using a register with a CostPerUse.
  // This is used by the ARC target, by the ARM Thumb and x86-64 targets, where
  // some registers require larger instruction encodings, by the RISC-V target,
  // where some registers preclude using some C instructions. By making it a
  // list, targets can have multiple cost models associated with each register
  // and can choose one specific cost model per Machine Function by overriding
  // TargetRegisterInfo::getRegisterCostTableIndex. Every target register will
  // finally have an equal number of cost values which is the max of costPerUse
  // values specified. Any mismatch in the cost values for a register will be
  // filled with zeros. Restricted the cost type to uint8_t in the
  // generated table. It will considerably reduce the table size.
  list<int> CostPerUse = [0];

  // CoveredBySubRegs - When this bit is set, the value of this register is
  // completely determined by the value of its sub-registers. For example, the
  // x86 register AX is covered by its sub-registers AL and AH, but EAX is not
  // covered by its sub-register AX.
  bit CoveredBySubRegs = false;

  // HWEncoding - The target specific hardware encoding for this register.
  bits<16> HWEncoding = 0;

  bit isArtificial = false;

  // isConstant - This register always holds a constant value (e.g. the zero
  // register in architectures such as MIPS)
  bit isConstant = false;

  /// PositionOrder - Indicate tablegen to place the newly added register at a later
  /// position to avoid iterations on them on unsupported target.
  int PositionOrder = 0;
}


Register
类是
RegisterWithSubRegs
类的基类 ——
RegisterWithSubRegs
类用于定义需要在
SubRegs
列表中指定子寄存器的寄存器,如下所示:



class RegisterWithSubRegs<string n, list<Register> subregs> : Register<n> {
  let SubRegs = subregs;
}

在 RISCV
RegisterInfo.td
文件中,为 RISCV架构定义了一些额外的寄存器类,一个是
Register
的子类
RISCVReg:



class RISCVReg<bits<5> Enc, string n, list<string> alt = []> : Register<n> {
  let HWEncoding{4-0} = Enc;
  let AltNames = alt;
}

另一个是
RegisterWithSubRegs的子类RISCVRegWithSubRegs:



class RISCVRegWithSubRegs<bits<5> Enc, string n, list<Register> subregs,
                          list<string> alt = []>
      : RegisterWithSubRegs<n, subregs> {
  let HWEncoding{4-0} = Enc;
  let AltNames = alt;
}

RISCV寄存器通过 5 位 ID 编号 进行标识,这是这些子类共有的一个特性。注意,这里使用了 “let” 表达式 来重写(override) 在超类(superclass) 中初始定义的值(例如,上面操作将AltNames字段进行了重写)。当然我们可以进一步继承
RISCVReg
类和
RISCVRegWithSubRegs
类来得到进一步细化的子类,还有一些其他的寄存器类,例如RISCVReg16:



class RISCVReg16<bits<5> Enc, string n, list<string> alt = []> : Register<n> {
  let HWEncoding{4-0} = Enc;
  let AltNames = alt;
}

定义的是16bit压缩指令寄存器(),注意,“16bit 压缩指令寄存器”指的是:当使用压缩(C 扩展)指令时,不是所有寄存器都能使用,只能使用一个固定的、缩减过的寄存器子集(x8–x15 或 f8–f15)。

关键点:它减少的是寄存器编号的 bit 数,而不是寄存器本身宽度。

我们来看一下具体寄存器的定义:


def F0_H  : RISCVReg16<0, "f0", ["ft0"]>, DwarfRegNum<[32]>;

这里定义了一个浮点寄存器,名字是f0,别名是ft0,DWARF调试器编号为32。F0_H(即 f0 / ft0)是 RISC-V 的浮点寄存器文件中的一个寄存器,它的硬件宽度始终是 64 bit,它既可用于单精度(F 扩展),也可用于双精度(D 扩展),也可用于半精度(Zfh)等。

定义一个寄存器类


RegisterClass
类(在
Target.td
中指定)用于定义一个对象,该对象表示一组相关的寄存器,并定义了这些寄存器的默认分配顺序。

一个使用
Target.td
的目标描述文件
XXXRegisterInfo.td
可以通过以下类来构建寄存器类:



// RegisterClass - Now that all of the registers are defined, and aliases
// between registers are defined, specify which registers belong to which
// register classes.  This also defines the default allocation order of
// registers by register allocators.
//
class RegisterClass<string namespace, list<ValueType> regTypes, int alignment,
                    dag regList, RegAltNameIndex idx = NoRegAltName>
  : DAGOperand {
  string Namespace = namespace;
 
  // The register size/alignment information, parameterized by a HW mode.
  RegInfoByHwMode RegInfos;
 
  // RegType - Specify the list ValueType of the registers in this register
  // class.  Note that all registers in a register class must have the same
  // ValueTypes.  This is a list because some targets permit storing different
  // types in same register, for example vector values with 128-bit total size,
  // but different count/size of items, like SSE on x86.
  //
  list<ValueType> RegTypes = regTypes;
 
  // Size - Specify the spill size in bits of the registers.  A default value of
  // zero lets tablegen pick an appropriate size.
  int Size = 0;
 
  // Alignment - Specify the alignment required of the registers when they are
  // stored or loaded to memory.
  //
  int Alignment = alignment;
 
  // CopyCost - This value is used to specify the cost of copying a value
  // between two registers in this register class. The default value is one
  // meaning it takes a single instruction to perform the copying. A negative
  // value means copying is extremely expensive or impossible.
  int CopyCost = 1;
 
  // MemberList - Specify which registers are in this class.  If the
  // allocation_order_* method are not specified, this also defines the order of
  // allocation used by the register allocator.
  //
  dag MemberList = regList;
 
  // AltNameIndex - The alternate register name to use when printing operands
  // of this register class. Every register in the register class must have
  // a valid alternate name for the given index.
  RegAltNameIndex altNameIndex = idx;
 
  // isAllocatable - Specify that the register class can be used for virtual
  // registers and register allocation.  Some register classes are only used to
  // model instruction operand constraints, and should have isAllocatable = 0.
  bit isAllocatable = true;
 
  // AltOrders - List of alternative allocation orders. The default order is
  // MemberList itself, and that is good enough for most targets since the
  // register allocators automatically remove reserved registers and move
  // callee-saved registers to the end.
  list<dag> AltOrders = [];
 
  // AltOrderSelect - The body of a function that selects the allocation order
  // to use in a given machine function. The code will be inserted in a
  // function like this:
  //
  //   static inline unsigned f(const MachineFunction &MF) { ... }
  //
  // The function should return 0 to select the default order defined by
  // MemberList, 1 to select the first AltOrders entry and so on.
  code AltOrderSelect = [{}];
 
  // Specify allocation priority for register allocators using a greedy
  // heuristic. Classes with higher priority values are assigned first. This is
  // useful as it is sometimes beneficial to assign registers to highly
  // constrained classes first. The value has to be in the range [0,31].
  int AllocationPriority = 0;
 
  // Force register class to use greedy's global heuristic for all
  // registers in this class. This should more aggressively try to
  // avoid spilling in pathological cases.
  bit GlobalPriority = false;

  // Generate register pressure set for this register class and any class
  // synthesized from it. Set to 0 to inhibit unneeded pressure sets.
  bit GeneratePressureSet = true;

  // Weight override for register pressure calculation. This is the value
  // TargetRegisterClass::getRegClassWeight() will return. The weight is in
  // units of pressure for this register class. If unset tablegen will
  // calculate a weight based on a number of register units in this register
  // class registers. The weight is per register.
  int Weight = ?;

  // The diagnostic type to present when referencing this operand in a match
  // failure error message. If this is empty, the default Match_InvalidOperand
  // diagnostic type will be used. If this is "<name>", a Match_<name> enum
  // value will be generated and used for this operand type. The target
  // assembly parser is responsible for converting this into a user-facing
  // diagnostic message.
  string DiagnosticType = "";

  // A diagnostic message to emit when an invalid value is provided for this
  // register class when it is being used as an assembly operand. If this is
  // non-empty, an anonymous diagnostic type enum value will be generated, and
  // the assembly matcher will provide a function to map from diagnostic types
  // to message strings.
  string DiagnosticString = "";

  // Target-specific flags. This becomes the TSFlags field in TargetRegisterClass.
  bits<8> TSFlags = 0;

  // If set then consider this register class to be the base class for registers in
  // its MemberList.  The base class for registers present in multiple base register
  // classes will be resolved in the order defined by this value, with lower values
  // taking precedence over higher ones.  Ties are resolved by enumeration order.
  int BaseClassOrder = ?;
}

定义一个
RegisterClass
,需要使用以下 4 个参数:

第一个参数是 ** 命名空间(namespace)** 的名称。

第二个参数是一个 ** 值类型(ValueType)** 列表,这些值类型在 /llvm/
include/llvm/CodeGen/ValueTypes.td
文件中定义。已定义的值类型包括整数类型(如
i16

i32
和用于布尔值的
i1
)、浮点类型(
f32

f64
)和向量类型(例如,
v8i16
表示一个包含 8 个
i16
元素的向量)。

一个
RegisterClass
中的所有寄存器必须具有相同的值类型。

但是,某些寄存器可能能够以不同的配置存储向量数据。例如,一个 128 位的向量寄存器可能能够处理 16 个 8 位整数元素、8 个 16 位整数元素、4 个 32 位整数元素,等等。

第三个参数指定了寄存器在被存储(store)到内存或从内存加载(load)时所需的对齐方式(alignment)。

最后一个参数
regList
指定了哪些寄存器属于这个类。

如果没有指定其他的分配顺序方法,
regList
也定义了寄存器分配器(register allocator)使用的分配顺序。

除了使用
(add R0, R1, ...)
这样的方式简单地列出寄存器外,还可以使用更高级的集合运算符(set operators)。

更多信息:请参见 /llvm/
include/llvm/Target/Target.td


RISCVRegisterInfo.td
中,定义了两个
RegisterClass
对象,首先是RISCVRegisterClass:



class RISCVRegisterClass<list<ValueType> regTypes, int align, dag regList>
    : RegisterClass<"RISCV", regTypes, align, regList> {
  bit IsVRegClass = 0;
  int VLMul = 1;
  int NF = 1;
 
  let Size = !if(IsVRegClass, !mul(VLMul, NF, 64), 0);
 
  let TSFlags{0} = IsVRegClass;
  let TSFlags{3-1} = !logtwo(VLMul);
  let TSFlags{6-4} = !sub(NF, 1);
}

参数1:是否是 RVV 向量寄存器类,0 → 普通的整数寄存器 / 浮点寄存器,1 → RVV 向量寄存器类;

参数2:向量寄存器组的 LMUL 值,RISC-V 向量寄存器可以按 LMUL 分组:

LMUL 占用物理寄存器数量 含义
1/8 1/8 个向量寄存器(理论) 不直接表达
1/4 1/4 个寄存器
1/2 1/2 个寄存器
1 一个向量寄存器 基础
2 两个连续寄存器 v0+v1
4 四个连续寄存器 v0..v3
8 八个连续寄存器 v0..v7

LLVM 中 LMUL 只允许正数 1、2、4、8。

参数3:RVV Load/Store Segment 指令的 NF(字段数),代表 需要 NF 组寄存器(每组大小为 LMUL),也就是总占用寄存器数:Total = VLMul × NF。

参数4:Size 仅用于 向量寄存器类,表示:
这个寄存器类一次操作的数据大小(bits)。


参数5:TSFlags —— 特殊编码(给指令调度器、寄存器分配器用)。

然后是GPRRegisterClass:



class GPRRegisterClass<dag regList>
    : RISCVRegisterClass<[XLenVT, XLenFVT, i32], 32, regList> {
  let RegInfos = XLenRI;
}

定义 RISC-V 的通用寄存器类,
RegInfos

RISCVRegisterClass
 父类定义的成员,用于存储「寄存器额外信息」(如寄存器是否可用于特定操作、是否是特殊寄存器(如 x0 是零寄存器)、寄存器编号映射等)。我们来看一个实例:



def FPR32 : RISCVRegisterClass<[f32], 32, (add
    (sequence "F%u_F", 15, 10),
    (sequence "F%u_F", 0, 7),
    (sequence "F%u_F", 16, 17),
    (sequence "F%u_F", 28, 31),
    (sequence "F%u_F", 8, 9),
    (sequence "F%u_F", 18, 27)
)>;

这里定义了一个定义 RISC-V 架构的 完整 32 位浮点寄存器类(FPR32)。

首先看一下三个传入参数:


[f32]
:该寄存器类支持的数据类型 —— 仅 
f32
(单精度浮点),说明这是专门用于 32 位浮点操作的寄存器。
32
:寄存器物理宽度(以位为单位)—— 32 位浮点寄存器的硬件宽度就是 32 位,固定不变(区别于 GPR 的 
XLen
 动态位宽)。
(add ...)
:该寄存器类包含的具体寄存器列表(核心部分),且 
(add A, B, C)
 的顺序决定了 LLVM 寄存器分配的优先级(先尝试分配 A 中的寄存器,再分配 B,以此类推)。


(add ...)
 内部用 
sequence
 指令批量生成寄存器名称(避免手动写 32 个寄存器),且顺序就是 LLVM 寄存器分配的优先级(先分配前面的寄存器,再分配后面的)。

1. 
sequence
 指令用法(批量生成寄存器)

格式:
(sequence "格式字符串", 起始编号, 结束编号)

格式字符串中的 
%u
 会被替换为数字(如 
F%u_F
 → 编号 0 → 
F0_F
,编号 15 → 
F15_F
);支持正序(起始 <结束)和倒序(起始> 结束)生成。

2. 逐段拆解寄存器(含优先级逻辑)

按 
(add ...)
 中的顺序,逐一解析每段寄存器的含义和设计目的:

代码段 生成的寄存器列表 数量 设计逻辑(为什么放这个位置)

(sequence "F%u_F", 15, 10)
F15_F、F14_F、…、F10_F(倒序) 6 个 「调用者保存寄存器」(Caller-Saved):函数调用时无需保存现场,分配成本低,优先用于临时数据

(sequence "F%u_F", 0, 7)
F0_F、F1_F、…、F7_F(正序) 8 个 同样是「调用者保存寄存器」,补充前面 6 个寄存器,扩大高优先级临时寄存器池

(sequence "F%u_F", 16, 17)
F16_F、F17_F(正序) 2 个 「被调用者保存寄存器」(Callee-Saved):函数调用时需保存现场,分配成本高,仅临时寄存器不足时用

(sequence "F%u_F", 28, 31)
F28_F、…、F31_F(正序) 4 个 同上(被调用者保存寄存器),进一步补充寄存器资源

(sequence "F%u_F", 8, 9)
F8_F、F9_F(正序) 2 个 特殊的「调用者保存寄存器」:因硬件编码限制,放在后面(优先级较低),但仍属于临时寄存器范畴

(sequence "F%u_F", 18, 27)
F18_F、…、F27_F(正序) 10 个 最后一批被调用者保存寄存器:资源充足,但分配成本最高,仅在前面所有寄存器都被占用时使用

3. 总计:32 个完整浮点寄存器

6 + 8 + 2 + 4 + 2 + 10 = 32 个,完全对应 RISC-V 
F
 扩展(32 位浮点扩展)的标准浮点寄存器集(F0-F31)。

使用 RISCV
RegisterInfo.td

TableGen
工具会生成多个输出文件,这些文件旨在被包含到你编写的其他源代码中。

RISCV
RegisterInfo.td
会生成 RISCV
GenRegisterInfo.h.inc
文件。该文件应被包含在你所编写的 RISCV寄存器实现的头文件(RISCV
RegisterInfo.h
)中。



#ifndef LLVM_LIB_TARGET_RISCV_RISCVREGISTERINFO_H
#define LLVM_LIB_TARGET_RISCV_RISCVREGISTERINFO_H
 
#include "llvm/CodeGen/TargetRegisterInfo.h"
 
#define GET_REGINFO_HEADER
#include "RISCVGenRegisterInfo.inc"
 
namespace llvm {
 
struct RISCVRegisterInfo : public RISCVGenRegisterInfo {
 
  RISCVRegisterInfo(unsigned HwMode);
 
  const uint32_t *getCallPreservedMask(const MachineFunction &MF,
                                       CallingConv::ID) const override;
 
  const MCPhysReg *getCalleeSavedRegs(const MachineFunction *MF) const override;

在 RISCV
GenRegisterInfo.h.inc
文件中,定义了一个名为 RISCV
GenRegisterInfo
的新结构体,它以
TargetRegisterInfo
为基类。

它还会基于已定义的寄存器类RISCVRegisterClass和GPRRegisterClass。

RISCV
RegisterInfo.td
还会生成  文件,该文件通常被包含在 RISCV 寄存器实现文件 的末尾。

下方的代码片段仅展示了部分生成的寄存器及其相关的寄存器类。

寄存器的顺序,反映了它们在目标描述文件(
.td
)中 定义时的顺序。



namespace {     // Register classes...
  // FPR32 Register Class...
  const MCPhysReg FPR32[] = {
    RISCV::F0_F, RISCV::F1_F, RISCV::F2_F, RISCV::F3_F, RISCV::F4_F, RISCV::F5_F, RISCV::F6_F, RISCV::F7_F, RISCV::F10_F, RISCV::F11_F, RISCV::F12_F, RISCV::F13_F, RISCV::F14_F, RISCV::F15_F, RISCV::F16_F, RISCV::F17_F, RISCV::F28_F, RISCV::F29_F, RISCV::F30_F, RISCV::F31_F, RISCV::F8_F, RISCV::F9_F, RISCV::F18_F, RISCV::F19_F, RISCV::F20_F, RISCV::F21_F, RISCV::F22_F, RISCV::F23_F, RISCV::F24_F, RISCV::F25_F, RISCV::F26_F, RISCV::F27_F, 
  };
 
  // FPR32 Bit set.
  const uint8_t FPR32Bits[] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x01, 
  };
 
  // GPR Register Class...
  const MCPhysReg GPR[] = {
    RISCV::X10, RISCV::X11, RISCV::X12, RISCV::X13, RISCV::X14, RISCV::X15, RISCV::X16, RISCV::X17, RISCV::X5, RISCV::X6, RISCV::X7, RISCV::X28, RISCV::X29, RISCV::X30, RISCV::X31, RISCV::X8, RISCV::X9, RISCV::X18, RISCV::X19, RISCV::X20, RISCV::X21, RISCV::X22, RISCV::X23, RISCV::X24, RISCV::X25, RISCV::X26, RISCV::X27, RISCV::X0, RISCV::X1, RISCV::X2, RISCV::X3, RISCV::X4, 
  };
 
  // GPR Bit set.
  const uint8_t GPRBits[] = {
    0xfe, 0xff, 0xff, 0xff, 0x01, 
  };
 
  // GPRNoX0 Register Class...
  const MCPhysReg GPRNoX0[] = {
    RISCV::X10, RISCV::X11, RISCV::X12, RISCV::X13, RISCV::X14, RISCV::X15, RISCV::X16, RISCV::X17, RISCV::X5, RISCV::X6, RISCV::X7, RISCV::X28, RISCV::X29, RISCV::X30, RISCV::X31, RISCV::X8, RISCV::X9, RISCV::X18, RISCV::X19, RISCV::X20, RISCV::X21, RISCV::X22, RISCV::X23, RISCV::X24, RISCV::X25, RISCV::X26, RISCV::X27, RISCV::X1, RISCV::X2, RISCV::X3, RISCV::X4, 
  };
 
  // GPRNoX0 Bit set.
  const uint8_t GPRNoX0Bits[] = {
    0xfc, 0xff, 0xff, 0xff, 0x01, 
  };
 
  // GPRNoX0X2 Register Class...
  const MCPhysReg GPRNoX0X2[] = {
    RISCV::X10, RISCV::X11, RISCV::X12, RISCV::X13, RISCV::X14, RISCV::X15, RISCV::X16, RISCV::X17, RISCV::X5, RISCV::X6, RISCV::X7, RISCV::X28, RISCV::X29, RISCV::X30, RISCV::X31, RISCV::X8, RISCV::X9, RISCV::X18, RISCV::X19, RISCV::X20, RISCV::X21, RISCV::X22, RISCV::X23, RISCV::X24, RISCV::X25, RISCV::X26, RISCV::X27, RISCV::X1, RISCV::X3, RISCV::X4, 
  };
 
  // GPRNoX0X2 Bit set.
  const uint8_t GPRNoX0X2Bits[] = {
    0xf4, 0xff, 0xff, 0xff, 0x01, 
  };
 
  // GPRTC Register Class...
  const MCPhysReg GPRTC[] = {
    RISCV::X5, RISCV::X6, RISCV::X7, RISCV::X10, RISCV::X11, RISCV::X12, RISCV::X13, RISCV::X14, RISCV::X15, RISCV::X16, RISCV::X17, RISCV::X28, RISCV::X29, RISCV::X30, RISCV::X31, 
  };
 
  // GPRTC Bit set.
  const uint8_t GPRTCBits[] = {
    0xc0, 0xf9, 0x07, 0xe0, 0x01, 
  };
 
  // FPR32C Register Class...
  const MCPhysReg FPR32C[] = {
    RISCV::F10_F, RISCV::F11_F, RISCV::F12_F, RISCV::F13_F, RISCV::F14_F, RISCV::F15_F, RISCV::F8_F, RISCV::F9_F, 
  };
 
  // FPR32C Bit set.
  const uint8_t FPR32CBits[] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x01, 
  };

寄存器分配器会避免使用保留寄存器(reserved registers),并且在所有易失性寄存器(volatile registers,上下文切换,值可能被修改且无显示保存的寄存器)都被用完之前,不会使用被调用者保存寄存器(callee saved registers)。通常这已经足够了,但在某些情况下,可能需要提供自定义的分配顺序(custom allocation orders)。

实现
TargetRegisterInfo
的子类

最后一步是手动编写
XXXRegisterInfo
的部分代码,该类实现了在
TargetRegisterInfo.h
中定义的接口(参见
TargetRegisterInfo
类)。除非被重写(overridden),否则这些函数默认返回 0、
NULL

false

以下是在
RISCVRegisterInfo.cpp
中为
RISCV
实现重写的部分函数列表如下(详细可参考
RISCVRegisterInfo.h
):

getCallPreservedMask返回值是 指向 
const uint32_t
 类型数组的指针

const uint32_t *
),核心作用是用位掩码(Bitmask)数组表示当前调用约定下的调用保存寄存器集(Callee-Saved Registers)。


getCalleeSavedRegs
返回一个 ** 被调用者保存寄存器(callee-saved registers)的列表,列表顺序按照期望的被调用者保存栈帧偏移(callee-save stack frame offset)** 排列。


getReservedRegs
返回一个按物理寄存器编号索引的位集(bitset),用于指示某个特定的寄存器是否不可用(unavailable)。


eliminateFrameIndex
从可能使用抽象帧索引(abstract frame indices)的指令中消除这些索引。

好了,至此我们介绍了LLVM中寄存器的定义,表示和实现方法。

© 版权声明

相关文章

暂无评论

none
暂无评论...