LLVM Cpu0 新后端2 第二部分

 想好好熟悉一下llvm开发一个新后端都要干什么,于是参考了老师的系列文章:

LLVM 后端实践笔记

代码在这里(还没来得及准备,先用网盘暂存一下):

链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?pwd=vd6s 提取码: vd6s  

目录

 一、第三节  2-3

1.1 新增的文件

1.1.1 Cpu0ISelDAGToDAG.cpp/.h

1.1.2 Cpu0SEISelDAGToDAG.cpp/.h

1.2 修改的文件

1.2.1 Cpu0TargetMachine.cpp

二、第四节  2-4

2.1 Cpu0CallingConv.td

2.2 Cpu0ISelLowering.cpp/.h

2.3 Cpu0InstrFormats.td

2.4 Cpu0InstrInfo.td

2.5 Cpu0SEInstrInfo.cpp/.h

2.6 Cpu0MachineFunctionInfo.h


 一、第三节  2-3

AsmPrinter 支持之后,已经可以将 Machine DAG 转成 asm 了,但我们现在还缺少将 LLVM IR DAG 转换成 Machine DAG 的功能,也就是指令选择的部分功能,在 LLVM 机器无关的目标代码生成中,指令选择占据了非常重要的地位。

指令选择的目的就是,把 DAG 中所有的 Node 都转换成目标相关的 Node,虽然在 Lowering LLVM IR 到 DAG 时,我们已经将部分 Node 转换了,但并不是所有,经过这个 pass 之后,所有的 Node 就都是目标机器支持的操作了。

但实际上需要我们做的工作并不是指令选择的功能,这些功能已经被 LLVM 实现了(感兴趣可以看看 lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp 中的实现),我们只需要继承已有实现,并将我们的指令系统支持进去(通过 tablegen)即可。这也便是 LLVM 模块化设计下的优势。

1.1 新增的文件

1.1.1 Cpu0ISelDAGToDAG.cpp/.h

Cpu0ISelDAGToDAG.cpp定义了 Cpu0DAGToDAGISel 类,继承自 SelectionDAGISel 类,并包含有一些全局化的接口,比如 Select 是指令选择的入口,其中会调用 trySelect 方法,后者是提供给子类的自定义部分指令选择方式的入口,可以先不管。在 Select 函数前边部分都没有选择成功的指令,会最后到SelectCode函数,这个函数是由 tablegen 依据 td 文件生成的 Cpu0GenDAGISel.inc 文件中定义的。

虽然 LLVM 的最终目标是让所有和平台相关的信息全部用 td 文件来描述,但目前还没有完全做到(毕竟不同的硬件差异还是挺大的,有些如 X86 的硬件设计还很复杂),所以这些无法用 td 描述的功能我们还需要依赖cpp文件来实现包括指令选择、寄存器、指令等等。

还有个 SelectAddr 函数,顾名思义是做关于地址模式的执行选择的,我们知道 IR DAG 中有些 Node 是地址操作数,这些 Node 可以很复杂,目前 Cpu0 把这块代码提出来特殊对待了。我们打开 Cpu0InstrInfo.td 中对 addr 记录的描述,就可以发现,之前已经在这里注册了一个处理函数名称,就叫 SelectAddr,实际上在 tablegen 指令选择时,也会对经由 addr 记录来描述的那些记录(显然会是一些地址 pattern),交给 SelectAddr 函数来处理。

// Complex patterns, e.g. X86 addressing mode, requires pattern matching code
// in C++. NumOperands is the number of operands returned by the select function;
// SelectFunc is the name of the function used to pattern match the max. pattern;
// RootNodes are the list of possible root nodes of the sub-dags to match.
// e.g. X86 addressing mode - def addr : ComplexPattern<4, "SelectAddr", [add]>;
//
class ComplexPattern<ValueType ty, int numops, string fn,
                     list<SDNode> roots = [], list<SDNodeProperty> props = [],
                     int complexity = -1> {
  ValueType Ty = ty;
  int NumOperands = numops;
  string SelectFunc = fn;
  list<SDNode> RootNodes = roots;
  list<SDNodeProperty> Properties = props;
  int Complexity = complexity;
}
// Cpu0 Address Mode! SDNode frameindex could posibily be a match since
// load and store instructions from stack used it.
def addr : ComplexPattern<iPTR, 2, "SelectAddr",
                          [frameindex], [SDNPWantParent]>;

我们之前在Cpu0InstrInfo.td指定了addr的选择函数就是SelectAddr,指令选择的时候就是判断addr是否满足我们SelectAddr内定义的条件。addr继承自ComplexPattern这个匹配类,我个人理解这个类叫做复杂匹配的原因就是为了定义一些td文件中没办法定义的复杂匹配规则,需要依赖cpp文件来实现(个人猜测,不确定对不对)。

1.1.2 Cpu0SEISelDAGToDAG.cpp/.h

Cpu0SEISelDAGToDAG.cpp定义了 Cpu0SEDAGToDAGISel 类,继承自 Cpu0DAGToDAGISel 类,这种双层设计,我们在前边已经描述过了。在这个底层的 SE 类中,实现了 trySelect 类,这个类目前还没有实现什么实质性的内容。目的就是将来留着处理 tablegen 无法自动处理的那些指令的指令选择。另外还实现了 createCpu0SEISelDAG 函数,用来做 Target 注册。

1.2 修改的文件

1.2.1 Cpu0TargetMachine.cpp

注册一个指令选择器。目前是将 Cpu0SEISelDAG 添加进来。addInstSelector 方法重写了父类 TargetPassConfig 的方法。

二、第四节  2-4

Mips 后端通过 jr $ra 来返回到Caller,$ra 是一个特殊寄存器,它用来保存调用者Caller的调用之后的下一条指令的地址,返回值会放到 $2 中。如果我们不对返回值做特殊处理,LLVM 会使用任意一个寄存器来存放返回值,这便与 Mips 的调用惯例不符。而且,LLVM 会为 jr 指令分配任意一个寄存器来存放返回地址。Mips 允许程序员使用其他寄存器代替 $ra,比如 jr $1,这样可以实现更加灵活的编程方式,节省时间。

这一部分,我们处理了函数调用时返回的操作,主要就是对针对 Cpu0 的特殊调用约定下的返回指令做约束,比如返回地址使用 $lr 来存储,返回值保存在特殊的寄存器中。函数 LowerReturn 正确处理了 return 的情况,函数创建了 Cpu0ISD::Ret 节点,并且里边包含了 %V0 寄存器的关联关系,这个寄存器保存了返回值。

文件修改如下:

2.1 Cpu0CallingConv.td

/// CCDelegateTo - This action invokes the specified sub-calling-convention.  It
/// is successful if the specified CC matches.
class CCDelegateTo<CallingConv cc> : CCAction {
  CallingConv CC = cc;
}
def RetCC_Cpu0EABI : CallingConv<[
  // i32 are returned in registers V0, V1, A0, A1
  CCIfType<[i32], CCAssignToReg<[V0, V1, A0, A1]>>
]>;
def RetCC_Cpu0 : CallingConv<[
  CCDelegateTo<RetCC_Cpu0EABI>
]>;

新增有关于返回的调用约定,增加RetCC_Cpu0 ,指定将 32 位整形返回值放到 V0、V1、A0、A1 这几个寄存器中。RetCC_Cpu0EABI用于定义返回值寄存器的calling convention,RetCC_Cpu0主要用于应用calling convention。

2.2 Cpu0ISelLowering.cpp/.h

我们新增了几个函数,主要是analyzeReturn和LowerReturn。我们新增了一些调用约定的分析函数,关键函数是 analyzeReturn()。该函数中利用了前边调用约定中定义的 RetCC_Cpu0 来分析返回值类型、值等信息,阻断不合法的情况。

其次,很重要的一个函数就是 LowerReturn(),该函数在早期将 ISD 的 ret 下降成 Cpu0ISD::Ret 节点。我们之前的实现是一句很简单的做法,也就是会忽略返回时的特殊约定,现在重新设计了这块的逻辑,也就是总生成 ret $lr 指令,以及对于sret属性的处理(参数被标注为sret属性表示这个参数会在函数调用内进行赋值修改)。

2.3 Cpu0InstrFormats.td

新增 Cpu0Pseudo伪指令类型,之后会用到。

2.4 Cpu0InstrInfo.td

利用刚才的伪指令 Pattern,定义新的 record,RetLR,它指定的 SDNode 是 Cpu0Ret,后者是我们之前定义好的。

define i32 @main() #0 {
  ret i32 0
}

上述IR在输出到汇编的过程中指令的转变:

constant 0匹配到addiu的原因是ret不接受立即数作为参数,我们在td文件中写过的。

def : Pat<(i32 immSExt16:$in), (ADDiu ZERO, imm:$in)>;

2.5 Cpu0SEInstrInfo.cpp/.h

增加伪指令展开部分的内容,也就是展开返回指令,选择 $lr 寄存器作为返回地址寄存器,选择 Cpu0::RET 作为指令。

2.6 Cpu0MachineFunctionInfo.h

增加了几个和返回寄存器、参数相关的辅助函数和成员变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值