Chinaunix首页 | 论坛 | 博客
  • 博客访问: 293691
  • 博文数量: 32
  • 博客积分: 665
  • 博客等级: 上士
  • 技术积分: 370
  • 用 户 组: 普通用户
  • 注册时间: 2009-07-25 11:20
文章分类

全部博文(32)

文章存档

2023年(1)

2021年(1)

2020年(2)

2018年(3)

2014年(1)

2013年(2)

2012年(9)

2011年(9)

2010年(2)

2009年(2)

分类: C/C++

2013-07-10 18:01:04

假定读者已经掌握的基本知识
1、清楚C++语言特性,类定义,模版,继承,对象实例等基本概念。
2、清楚LLVM中间表示的大概情况,最好已经有过LLVM前端的实现经验。
3、清楚任意一种硬件的汇编语言。

第1:理解LLVM后端的大套路

官方文档中所述的编写一个后端的基本步骤
1、建立一个TargetMachine的子类用于描述你的硬件特性。可以简单的拷贝一个现有的例子,然后改改名字。

2、描述目标机器的寄存器集。使用TableGen从目标机器的RegisterInfo.td中生成寄存器定义信息、同名信息、寄存器组信息。你还需要编写TargetRegisterInfo子类来说明寄存器如何分派和描述寄存器之间的关系。

3、描述目标机器的指令集。同样使用TableGen从目标机器的TargetInstrFormats.td和TargetInstrInfo.td中生成指令信息。你还需要编写TargetInstrInfo子类来说明目标机器所支持的机器指令。

4、描述如何选择和转化LLVM IR,从一个DAG表示的指令到目标机器的本地指令。使用TableGen靠TargetInstrInfo.td提供的信息,生成模式匹配成功的指令。编写代码XXXISelDAGToDAG.cpp来说明如何匹配模式和DAG-to-DAG指令选择等。同时还要编写XXXISelLowering.cpp来替换或者移除目标机器不支持的类型操作。

5、编写汇编语言打印代码,来转换LLVM IR到GAS格式的汇编。

6、(可选)支持subtargets。

7、(可选)增加JIT支持和建立一个机器码生成器,用于直接生成二进制码到内存。

这些基本步骤是官方文档刚上来就给读者们摆出来的,是宏观角度观看整个事情的各个步骤,在对LLVM后端不了解的情况,根本不可能知道这些步骤都是在干嘛。SelectionDAG和这些td文件们都是个啥东西,还有这个XXXISelLowering具体是在干什么。
对于这些步骤而言目前仅需知道这样一个事实即可:
编写一个后端,需要做的事情是复制一个已有的后端目录,然后改下里面的目标机名字,另外主要修改RegisterInfo.td、TargetInstrFormats.td、TargetInstrInfo.td、XXXISelDAGToDAG.cpp、XXXISelLowering.cpp这几种目标机相关的东西。
LLVM后端的大套路基本就是由这几部分组成的。下面分别简述这几部分的作用。
RegisterInfo.td:说明目标机都由哪些寄存器,分别用于什么数据类型。以及同种数值类型寄存器的分组,另外还说明了寄存器的子寄存器和别名寄存器,例如x86里的ax寄存器有al,ah两个子寄存器,ebx是bx的别名寄存器。
TargetInstrFormats.td:说明指令分类,有的目标机有着明显的指令类别,如带跳转属性的类别,有访问内存的类别,有运算类别等。也可以按指令长度,操作数个数等属性分类。有了分类,就可以在定义指令时,指定指令是哪一类。
TargetInstrInfo.td:说明和定义具体的机器指令,指定指令的分类、指令码(opcode)、操作数样式等信息。另外,定义一条目标机指令时,还要说明如何从IR中转化为这种指令,一般是符合某种模式的IR片段可以转化为一个相应目标机指令,说明如何从IR中转化为这种指令,就是在定义这种模式。还有一些较为复杂的模式(complexpattern),在td中说明比较困难,此情况用手写c++代码(一般写在XXXISelDAGToDAG.cpp中)来进行模式匹配。
XXXISelLowering.cpp:变换指令选择dag到更接近目标机的dag表示,这里主要做两种事情:一个事情是把支持的类型处理掉,一般遇到某种目标机不支持的数据类型就扩充或者缩短为另一种支持的类型,靠在TargetLowering子类中:
setOperationAction(ISD::SDIV, MVT::i8, Expand); //在IR中会有i8的"有符号除",而目标机"有符号除"指令支持不了i8,需进行扩充

另一个事情是简化dag或者变换dag。
比如IR中将一个寄存器和一个内存地址的数相加:
...
%2 = load i32 %a
%3 = add i32 %2, %1

若目标机加指令操作数有内存寻址的能力,则无需有load过程,可将其简化为一个目标机定义的加节点(操作一个寄存器一个内存地址的加法指令)。

再举一个变换的例子:
假设目标机没有寄存器之间的mov操作指令,这时就要寻找相应的替代方案。使用加法指令
add 寄存器1, 常数0 -> 寄存器2
这样来实现
mov 寄存器1 -> 寄存器2
这种转化较为简单,也可以在TargetInstrInfo.td中用形如
def : Pat<(mov Int32Regs:$r1, Int32Regs:$r2), (XXXadd ZERO, Int32Regs:$r1, Int32Regs:$r2)>;
做到,但一般def : Pat<(),()>仅对较为简单的直接变换时使用,当变换的同时需要手动设定某些属性等额外操作时,还是要放到lowering里做变换。

第x:大套路的数据分布
第x:大套路的
第x:定义寄存RegisterInfo.td
第x:定义指令TargetInstrInfo.td

阅读(7076) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~