对经过前端翻译后生成的 LLVM 中间代码,通过后端代码生成器可以生成对特定后端处理器的后端代码。生成的后端代码可以两种形式存在:一种是以目标处理器的汇编代码形式,可以通过汇编器编译后得到相应的目标处理器二进制代码, 并能运行在目标处理器上;另一种是直接以二进制代码存在,不能运行在目标处理 器上,但可以使用 JIT 编译器直接在本地运行。
有用的抽象类::TargetMachine、TargetData、TargetLowering、MRegisterInfo、TargetInstrInfo、TargetFrameInfo、TargetSubtarget、TargetJITInfo 等。
后端移植结构如下图:
主要从两方面来描述目标处理器的寄存器,实现关于目标处理器寄存器的TableGen 描述以及继承并实现 MRegisterInfo 类,也即需要实现文件件“XXXRegisterInfo.td”、“XXXRegisterInfo.h”和“XXXRegisterInfo.cpp”。
在文件“XXXRegisterInfo.td”中,需要描述目标处理器每个寄存器的属性, 寄存器之间的别名关系以及程序运行时的寄存器分配方案等,这通过 TableGen 提 供的描述寄存器的四个记录类实现:Register、RegisterGroup、RegisterClass、 DwarfRegNum
Register 类 用于描述每个寄存器的属性,该类包括的数据有:
数据 | 解释 |
---|---|
string Namespace | 指定所属的命名空间 |
string Name | 寄存器的名字 |
int SpillSize | 寄存器溢出时保存寄存器所需的位的个数 |
int SpillAlignment | 寄存器溢出时保存寄存器要求的对齐 |
list Aliases | 读/写本寄存器将读/写的其它寄存器的列表(别名) |
int DwarfNumber gcc/gdb | 内部用于识别寄存器的编号 |
例如,下面建立了一个名为 GPR 的寄存器类,属于命名空间“XXX”,有一 个长度为 2 的位串类型数据 num.。用 GPR 可以建立 4 个记录定义 R0~R3,描述 了 4 个属于命名空间“XXX”的寄存器。
class GPR< bits<2> num, string n > :
Register{
field bits<2> Num;
let Namespace = "XXX";
Num = num;
}
def R0 : GPR< 0, "R0">;
def R1 : GPR< 1, "R1">;
def R2 : GPR< 2, "R2">;
def R3 : GPR< 3, "R3">;
RegisterGroup 类 继承了 Register 类,用于描述有别名的寄存器,其结构如下:
class RegisterGroup aliases> :
Register{
let Aliases = aliases; }
例如,“def S0 : RegisterGroup<“S0”, [R0]>;”说明了 S0 与 R0 之间的别名关系。
RegisterClass 类 帮助对寄存器进行分类,将不同功能的寄存器分为不同的寄存器类,并规定不同类型的寄存器在进行寄存器分配时的分配顺序。
类包括的数据有:
数据 | 解释 |
---|---|
string Namespace = namespace | 指定所属的命名空间 |
list RegTypes | 指令该类寄存器类型 |
int Size | 寄存器溢出时保存寄存器所需的位的个数 |
int Alignment | 当寄存器被写入内存时需要的对齐 |
list MemberList | 列出所有属于本寄存器类的寄存器,并作为没有提供寄存器分配方案 allocation_order_*时默认的寄存器分配顺序 |
code MethodProtos | 提供寄存器分配方案 allocation_order_*的函数原型 |
code MethodBodies | 实现寄存器分配方案 allocation_order_*函数 |
以下给出了一个寄存器类的记录定义:
def GPRC : RegisterClass< "XXX", [i32], 32, [R0, R1, R2, R3] >
{ let MethodProtos = [{
iterator allocation_order_begin(MachineFunction &MF) const;
iterator allocation_order_end(MachineFunction &MF) const;
}];
let MethodBodies = [{
GPRCClass::iterator GPRCClass::allocation_order_begin(
MachineFunction &MF) const
{ return begin() + 1; }
GPRCClass::iterator GPRCClass::allocation_order_end(
MachineFunction &MF) const {
return end() - 1; }
}];
}
上面定义的寄存器类 GPRC 包含 4 个寄存器 R0、R1、R2、R3,该寄存器类中的寄存器的类型为 i32,对齐为 32,所属命名空间的名字为“XXX”,并且在allocation_order_begin 和 allocation_order_end 的函数实现中分别规定 R0 和 R3 不参与寄存器分配。
DwarfRegNum 类 提供了 llvm 寄存器与 gcc/gdb 寄存器编号之间的映射,其结构如下:
class DwarfRegNum{
int DwarfNumber = N;
}
例如,“def R0 : GPR< 0, “R0”>, DwarfRegNum<0>;”将 R0 映射到寄存器编号 0。 对需移植的目标处理器,通过以上这四个记录类来描述寄存器,就可完成文件“XXXRegisterInfo.td”的实现。
在文件 “ XXXRegisterInfo.h ” 和 “ XXXRegisterInfo.cpp ” 中 , 则 需 继 承MRegisterInfo 类,并实现 MRegisterInfo 类中的虚函数。这些虚函数接口主要有:
virtual void storeRegToStackSlot(MachineBasicBlock &MBB,
MachineBasicBlock::iterator MI, unsigned SrcReg,
int FrameIndex, const TargetRegisterClass *RC) const = 0;
virtual void loadRegFromStackSlot(MachineBasicBlock &MBB,
MachineBasicBlock::iterator MI, unsigned DestReg,
int FrameIndex, const TargetRegisterClass *RC) const = 0;
virtual void copyRegToReg(MachineBasicBlock &MBB,
MachineBasicBlock::iterator MI, unsigned DestReg,
unsigned SrcReg, const TargetRegisterClass *RC) const = 0;
virtual MachineInstr* foldMemoryOperand(MachineInstr* MI,
unsigned OpNum, int FrameIndex) const;
virtual void eliminateCallFramePseudoInstr(MachineFunction &MF,
MachineBasicBlock &MBB, MachineBasicBlock::iterator MI) cons;
virtual void processFunctionBeforeFrameFinalized(MachineFunction &MF) const;
virtual void eliminateFrameIndex(MachineBasicBlock::iterator MI) const;
virtual void emitPrologue(MachineFunction &MF) const = 0;
virtual void emitEpilogue(MachineFunction &MF, MachineBasicBlock &MBB) const = 0;
virtual int getDwarfRegNum(unsigned RegNum) const = 0;
virtual unsigned getFrameRegister(MachineFunction &MF) const = 0;
virtual unsigned getRARegister() const = 0;
ML virtual void getLocation(MachineFunction &MF, unsigned Index, MachineLocation &ML) const;
virtual void getInitialFrameState(std::vector &Moves) const;