LLVM Cpu0 新后端6

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

LLVM 后端实践笔记

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

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

之前的章节只实现了 int 和 32 位的 long 类型数据,这一章会新增一些更复杂的数据类型,比如 char, bool, short, long long,还会增加结构体,浮点,和向量类型。这一部分内容相对比较简单,其实这些类型也都是标准语言都支持的类型,所以 LLVM 自身已经实现了很大一部分功能,只要我们的后端不那么奇怪,就很容易填补缺失的内容。

目录

一、修改的文件

[1.1 Cpu0ISelDAGToDAG.cpp](#1.1 Cpu0ISelDAGToDAG.cpp)

[1.2 Cpu0ISelLowering.cpp/.h](#1.2 Cpu0ISelLowering.cpp/.h)

[1.3 Cpu0InstrInfo.td](#1.3 Cpu0InstrInfo.td)

[1.4 Cpu0SEISelDAGToDAG.cpp/.h](#1.4 Cpu0SEISelDAGToDAG.cpp/.h)

[1.5 MCTargetDesc/Cpu0InstPrinter.cpp/.h](#1.5 MCTargetDesc/Cpu0InstPrinter.cpp/.h)

二、实现结果

[2.1 局部指针](#2.1 局部指针)

[2.2 char类型](#2.2 char类型)

[2.3 bool类型](#2.3 bool类型)

[2.4 short](#2.4 short)

[2.5 long long 类型](#2.5 long long 类型)

[2.6 局部数组、结构体](#2.6 局部数组、结构体)

[2.7 全局数组、结构体](#2.7 全局数组、结构体)

[2.8 向量](#2.8 向量)

[2.9 cl指令](#2.9 cl指令)


一、修改的文件

1.1 Cpu0ISelDAGToDAG.cpp

在SelectAddr接口内增加对于基址+常量偏移这种地址形式的处理,对于全局基址加常量偏移的情况,提取其基址和偏移。。

1.2 Cpu0ISelLowering.cpp/.h

有关于对 bool 类型的处理,这里增加了一些对 i1 类型 Promote 的合法化描述,告诉 LLVM 在遇到对 i1 类型的 extend 时要做 Promote。Promote 是将较小宽度的数据类型扩展成对应的能够支持的更宽的数据宽度类型,在指令选择的类型合法化阶段会起到作用。在 long long 实现中,在 Lowering 的位置还需要增加对 long long 类型的移位操作合法化。

覆盖一个函数 isOffsetFoldingLegal(),直接返回 false,避免带偏移的全局地址展开,Cpu0 和 Mips 一样无法处理这种情况。我们实现的 getAddrNonPIC() 方法中,将全局符号地址展开成一条加法指令,对地址的高低位做加法运算。所以实际上我们会将一条全局地址带偏移的寻址展开成加法运算 base,然后再把结果与 offset 相加的 DAG(在 Cpu0ISelDAGToDAG.cpp 中的 SelectAddr 中提取这种情况的 node Value,此时就已经是两个 add node 了)。

最后,还需要对向量类型的支持做一小部分改动,覆盖 getSetCCResultType() 方法,如果是向量类型,使用 VT.changeVectorElementTypeToInteger() 方法返回 CC 值。

1.3 Cpu0InstrInfo.td

到目前,因为我们添加数据类型的很多实现代码已经在公共 LLVM 代码中实现,所以实际上大多数修改都在 td 文件中。

新增一个 mem_ea 的操作数类型,这是一个 complexpattern,会定义其 encoding 操作和 printinst 等操作,它用来描述指令 pattern 中的地址表示;然后要定义一个 LEA_ADDiu 的模式,这是一个不会输出成指令的模式,它实际上是计算地址+偏移的结果,这和 sparc 处理器中的 LEA_ADDRi 是一样的效果。

新增 i8 和 i16 相关的 extend 类型以及对应的 ld/st,命名为 LB, LBu, SB, LH, LHu, SH。LB, LH 处理有符号的 i8/i16 类型 load,LBu, LHu 处理无符号的 i8/i16 类型 load,SB, SH 处理i8/i16 类型的store。

新增 CountLeading0 和 CountLeading1 的 pattern,用来选择到计算前导 0 和计算前导 1 的指令,llvm 内置了 ctlz 的 node(count leading zero),可以直接把 clz 指令接过去,不过对于 count leading 1 是没有对应的 node 的,不过可以通过先对值取反然后求前导 0 的方式实现前导 1 的计算,即 ctlz (not RC:$rb)。

因为 C 语言没有对求前导 0 和前导 1 的原生语法,所以实际上会使用 builtin 接口来实现,也就是说,在 C 语言描述中,为了实现这种功能,需要调用 __builtin_clz() 函数(ctls 就是先对参数取反再调用 ctlz 的 builtin),因为我们使用了内置的 node,所以这部分是 llvm 帮我们实现了。

1.4 Cpu0SEISelDAGToDAG.cpp/.h

定义了一个 selectAddESubE() 方法,用来处理带进位的加减法运算的指令选择。在 trySelect() 方法中,将对 ISD::SUBE, ISD::ADDE 的情况选择用 selectAddESubE() 来处理。

selectAddESubE() 方法为符合条件的 node 新增了一个操作数节点,该节点会读取状态字中进位是否是 1,并将结果叠加到运算中;在 Cpu032I 处理器中,使用 CMP 指令和 ANDi 指令来获取进位状态,在 Cpu032II 处理器中,则使用 SLT 指令直接判断进位。

另外,还要处理 SMUL_LOHI 和 UMUL_LOHI 节点,这是能够直接返回两个运算结果的节点(高低位)。

1.5 MCTargetDesc/Cpu0InstPrinter.cpp/.h

增加mem_ea 的printinst操作的实现。

二、实现结果

2.1 局部指针

cpp 复制代码
int test_local_pointer() {
  int b = 3;
  int *p = &b;
  return *p;
}
bash 复制代码
	addiu	$sp, $sp, -8   # 扩栈
	addiu	$2, $zero, 3   # 将3存到寄存器
	st	$2, 4($sp)         # 将其存到栈上
	addiu	$2, $sp, 4     # 读出栈中局部变量的地址
	st	$2, 0($sp)         # 将这个地址存到栈上
	ld	$2, 0($sp)         # 读出这个地址
	ld	$2, 0($2)          # 读出地址里的内容
	addiu	$sp, $sp, 8    # 回栈
	ret	$lr                # 返回

2.2 char类型

cpp 复制代码
struct Date
{
  short year;
  char month;
  char day;
  char hour;
  char minute;
  char second;
};

unsigned char b[4] = {'a', 'b', 'c', '\0'};

int test_char()
{
  unsigned char a = b[1];
  char c = (char)b[1];
  struct Date date1 = {2021, (char)2, (char)27, (char)17, (char)22, (char)10};
  char m = date1.month;
  char s = date1.second;

  return 0;
}
bash 复制代码
	addiu	$sp, $sp, -24
	lui	$2, %got_hi(b)
	addu	$2, $2, $gp
	ld	$2, %got_lo(b)($2)  # 与上边搭配加载全局变量b的首地址
	lbu	$3, 1($2)           # 计算b[1]的地址,存到寄存器3
	sb	$3, 20($sp)         # 将寄存器3内的地址存到栈上
	lbu	$2, 1($2)           # 再次计算b[1]的地址,存到寄存器2
	sb	$2, 16($sp)         # 将寄存器2内的地址存到栈上
	ld	$2, %got($__const.test_char.date1)($gp)
	ori	$2, $2, %lo($__const.test_char.date1)  # 获取要写入局部变量对象的常量的地址
	lhu	$3, 6($2)           # 将偏移6处的内容load到寄存器3中,lhu是i16的,就是load范围是2字节
	lhu	$4, 4($2)           # 将偏移4处的内容load到寄存器4中,lhu是i16的,就是load范围是2字节
	shl	$4, $4, 16         
	or	$3, $4, $3          # 这两条是将上述load出来的两个2字节的内容拼成一个4字节的
	st	$3, 12($sp)         # 将这4字节存到栈上,也就是存放hour, minute, second到 date1
	lhu	$3, 2($2)           # 这里是相同的逻辑
	lhu	$2, 0($2)
	shl	$2, $2, 16
	or	$2, $2, $3
	st	$2, 8($sp)          # 将这4字节存到栈上,也就是存放year, month, day到 date1
	lbu	$2, 10($sp)         # 从偏移10的位置读出date1.month(我们知道year, month, day在偏移8的位置,year两个字节,因此month在偏移10的位置,这里很正确)
	sb	$2, 4($sp)          # 将其存到栈上(m)
	lbu	$2, 14($sp)         # 从偏移14的位置读出date1.second
	sb	$2, 0($sp)          # 将其存到栈上(s)
	addiu	$2, $zero, 0
	addiu	$sp, $sp, 24
	ret	$lr

2.3 bool类型

cpp 复制代码
bool test_load_bool()
{
  int a = 1;

  if (a < 0)
    return false;

  return true;
}

这里涉及到跳转我们当前可能编不过,下一节的内容加上之后我们就可以编过了,我们先提前看一下效果。

bash 复制代码
_Z14test_load_boolv:
# %bb.0:
	addiu	$sp, $sp, -8
	addiu	$2, $zero, 1
	st	$2, 0($sp)
	ld	$2, 0($sp)
	addiu	$3, $zero, -1
	slt	$2, $3, $2
	bne	$2, $zero, $BB0_2
	nop
# %bb.1:
	addiu	$2, $zero, 0
	sb	$2, 7($sp)       # 使用 sb 将 bool 类型的 0 写入栈
	jmp	$BB0_3
$BB0_2:
	addiu	$2, $zero, 1
	sb	$2, 7($sp)       # 使用 sb 将 bool 类型的 1 写入栈
$BB0_3:
	lbu	$2, 7($sp)
	addiu	$sp, $sp, 8
	ret	$lr

2.4 short

cpp 复制代码
int test_signed_char()
{
  char a = 0x80;
  int i = (signed int)a;
  i = i + 2; // i = (-128 + 2) = -126

  return i;
}

int test_unsigned_char()
{
  unsigned char c = 0x80;
  unsigned int ui = (unsigned int)c;
  ui = ui + 2; // ui = (128 + 2) = 130

  return (int)ui;
}

int test_signed_short()
{
  short a = 0x8000;
  int i = (signed int)a;
  i = i + 2; // i = (-32768 + 2) = -32766

  return i;
}

int test_unsigned_short()
{
  unsigned short c = 0x8000;
  unsigned int ui = (unsigned int)c;
  ui = ui + 2; // ui = (32768 + 2) = 32770

  return (int)ui;
}
bash 复制代码
st_signed_short
...
	addiu	$sp, $sp, -8
	ori	$2, $zero, 32768
	sh	$2, 4($sp)
	lh	$2, 4($sp)
	st	$2, 0($sp)
	ld	$2, 0($sp)
	addiu	$2, $2, 2
	st	$2, 0($sp)
	ld	$2, 0($sp)
	addiu	$sp, $sp, 8
	ret	$lr
...
test_unsigned_short:
...
	addiu	$sp, $sp, -8
	ori	$2, $zero, 32768
	sh	$2, 4($sp)
	lhu	$2, 4($sp)
	st	$2, 0($sp)
	ld	$2, 0($sp)
	addiu	$2, $2, 2
	st	$2, 0($sp)
	ld	$2, 0($sp)
	addiu	$sp, $sp, 8
	ret	$lr
...

汇编还是很好理解的,这里就不进行详细的分析了。

2.5 long long 类型

cpp 复制代码
long long test_longlong()
{
  long long a = 0x300000002;
  long long b = 0x100000001;
  int a1 = 0x30010000;
  int b1 = 0x20010000;

  long long c = a + b;    // c = 0x00000004,00000003
  long long d = a - b;    // d = 0x00000002,00000001
  long long e = a * b;    // e = 0x00000005,00000002
  long long f = (long long)a1 * (long long)b1;    // f = 0x00060050,01000000

  return (c+d+e+f);       // (0x0006005b,01000006) = (393307,16777222)
}
bash 复制代码
	addiu	$sp, $sp, -56
	addiu	$2, $zero, 2
	st	$2, 52($sp)          # a的低位
	addiu	$2, $zero, 3
	st	$2, 48($sp)          # a的高位
	addiu	$2, $zero, 1
	st	$2, 44($sp)          # b的低位
	st	$2, 40($sp)          # b的高位
	lui	$2, 12289
	st	$2, 36($sp)          # a1
	lui	$2, 8193
	st	$2, 32($sp)          # b1
	ld	$2, 52($sp)          # a的低位
	ld	$3, 48($sp)          # a的高位
	ld	$4, 44($sp)          # b的低位
	ld	$5, 40($sp)          # b的高位
	addu	$3, $3, $5       # 高位相加
	addu	$4, $2, $4       # 低位相加
	sltu	$2, $4, $2       # 判断低位加法是否有进位
	addu	$2, $3, $2       # 将进位与高位结果相加
	st	$4, 28($sp)          # 下同
	st	$2, 24($sp)
	ld	$2, 48($sp)
	ld	$3, 52($sp)
	ld	$4, 40($sp)
	ld	$5, 44($sp)
	sltu	$6, $3, $5
	subu	$2, $2, $4
	subu	$2, $2, $6
	subu	$3, $3, $5
	st	$3, 20($sp)
	st	$2, 16($sp)
	ld	$2, 48($sp)
	ld	$3, 52($sp)
	ld	$4, 44($sp)
	ld	$5, 40($sp)
	mul	$5, $3, $5
	multu	$3, $4
	mflo	$3
	mfhi	$6
	addu	$5, $6, $5
	mul	$2, $2, $4
	addu	$2, $5, $2
	st	$3, 12($sp)
	st	$2, 8($sp)
	ld	$2, 36($sp)
	ld	$3, 32($sp)
	mult	$2, $3
	mflo	$2
	mfhi	$3
	st	$2, 4($sp)
	st	$3, 0($sp)
	ld	$2, 28($sp)
	ld	$3, 24($sp)
	ld	$4, 20($sp)
	ld	$5, 16($sp)
	addu	$3, $3, $5
	addu	$4, $2, $4
	sltu	$2, $4, $2
	addu	$2, $3, $2
	ld	$3, 8($sp)
	ld	$5, 12($sp)
	addu	$5, $4, $5
	sltu	$4, $5, $4
	addu	$2, $2, $3
	addu	$2, $2, $4
	ld	$3, 4($sp)
	ld	$4, 0($sp)
	addu	$2, $2, $4
	addu	$3, $5, $3
	sltu	$4, $3, $5
	addu	$2, $2, $4
	addiu	$sp, $sp, 56
	ret	$lr

2.6 局部数组、结构体

与2.2中的局部结构体类似。

2.7 全局数组、结构体

cpp 复制代码
struct Date
{
  int year;
  int month;
  int day;
};

struct Date date = {2021, 2, 27};
int a[3] = {2021, 2, 27};

int test_struct()
{
  int day = date.day;
  int i = a[1];

  return (i+day);  // 2 + 27 = 29
}
bash 复制代码
	addiu	$sp, $sp, -8
	lui	$2, %got_hi(date)
	addu	$2, $2, $gp
	ld	$2, %got_lo(date)($2)  # 从got表中取全局变量date的地址
	ld	$2, 8($2)              # 从偏移8的地方load出date.day,(year和month各占4字节)
	st	$2, 4($sp)             # 存到栈中(day)
	lui	$2, %got_hi(a)         # 下同
	addu	$2, $2, $gp
	ld	$2, %got_lo(a)($2)
	ld	$2, 4($2)
	st	$2, 0($sp)
	ld	$2, 0($sp)
	ld	$3, 4($sp)
	addu	$2, $2, $3
	addiu	$sp, $sp, 8
	ret	$lr

2.8 向量

cpp 复制代码
typedef long vector8long __attribute__((__vector_size__(32)));
typedef long vector8short __attribute__((__vector_size__(16)));

int test_cmplt_short()
{
  volatile vector8short a0 = {0, 1, 2, 3};
  volatile vector8short b0 = {2, 2, 2, 2};
  volatile vector8short c0;
  c0 = a0 < b0;

  return (int)(c0[0] + c0[1] + c0[2] + c0[3]);
}

int test_cmplt_long()
{
  volatile vector8long a0 = {2, 2, 2, 2, 1, 1, 1, 1};
  volatile vector8long b0 = {1, 1, 1, 1, 2, 2, 2, 2};
  volatile vector8long c0;
  c0 = a0 < b0;

  return (c0[0] + c0[1] + c0[2] + c0[3] + c0[4] + c0[5] + c0[6] + c0[7]);
}

下述是test_cmplt_short函数的汇编,我们看个稍微短一点的:

bash 复制代码
	addiu	$sp, $sp, -64
	st	$10, 60($sp)                    # 4-byte Folded Spill
	st	$9, 56($sp)                     # 4-byte Folded Spill
	addiu	$2, $zero, 3
	st	$2, 44($sp)
	addiu	$3, $zero, 2
	st	$3, 40($sp)
	addiu	$2, $zero, 1
	st	$2, 36($sp)
	addiu	$2, $zero, 0
	st	$2, 32($sp)
	st	$3, 28($sp)
	st	$3, 24($sp)
	st	$3, 20($sp)
	st	$3, 16($sp)
	ld	$3, 44($sp)
	ld	$4, 40($sp)
	ld	$5, 36($sp)
	ld	$6, 32($sp)
	ld	$7, 28($sp)
	ld	$8, 24($sp)
	ld	$9, 20($sp)
	ld	$10, 16($sp)
	slt	$6, $6, $10
	subu	$6, $2, $6
	slt	$5, $5, $9
	subu	$5, $2, $5
	slt	$4, $4, $8
	subu	$4, $2, $4
	slt	$3, $3, $7
	subu	$2, $2, $3
	st	$2, 12($sp)
	st	$4, 8($sp)
	st	$5, 4($sp)
	st	$6, 0($sp)
	ld	$2, 12($sp)
	ld	$2, 8($sp)
	ld	$2, 4($sp)
	ld	$2, 0($sp)
	ld	$3, 12($sp)
	ld	$3, 8($sp)
	ld	$3, 0($sp)
	ld	$3, 4($sp)
	addu	$2, $2, $3
	ld	$3, 12($sp)
	ld	$3, 4($sp)
	ld	$3, 0($sp)
	ld	$3, 8($sp)
	addu	$2, $2, $3
	ld	$3, 8($sp)
	ld	$3, 4($sp)
	ld	$3, 0($sp)
	ld	$3, 12($sp)
	addu	$2, $2, $3
	ld	$9, 56($sp)                     # 4-byte Folded Reload
	ld	$10, 60($sp)                    # 4-byte Folded Reload
	addiu	$sp, $sp, 64
	ret	$lr

其实整体逻辑是很简单的。

2.9 cl指令

cpp 复制代码
int countLeadingZero() {
  int a, b;
  b = __builtin_clz(a);
  return b;
}

int countLeadingOne() {
  int a, b;
  b = __builtin_clz(~a);
  return b;
}
bash 复制代码
countLeadingZero:
	addiu	$sp, $sp, -8
	ld	$2, 4($sp)
	clz	$2, $2
	st	$2, 0($sp)
	ld	$2, 0($sp)
	addiu	$sp, $sp, 8
	ret	$lr

countLeadingOne:
	addiu	$sp, $sp, -8
	ld	$2, 4($sp)
	clo	$2, $2
	st	$2, 0($sp)
	ld	$2, 0($sp)
	addiu	$sp, $sp, 8
	ret	$lr
相关推荐
獨枭1 分钟前
C++ 项目中使用 .dll 和 .def 文件的操作指南
c++
霁月风4 分钟前
设计模式——观察者模式
c++·观察者模式·设计模式
橘色的喵5 分钟前
C++编程:避免因编译优化引发的多线程死锁问题
c++·多线程·memory·死锁·内存屏障·内存栅栏·memory barrier
一颗松鼠8 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
有梦想的咸鱼_10 分钟前
go实现并发安全hashtable 拉链法
开发语言·golang·哈希算法
海阔天空_201316 分钟前
Python pyautogui库:自动化操作的强大工具
运维·开发语言·python·青少年编程·自动化
天下皆白_唯我独黑23 分钟前
php 使用qrcode制作二维码图片
开发语言·php
QAQ小菜鸟26 分钟前
一、初识C语言(1)
c语言
夜雨翦春韭27 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
小远yyds28 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js