LLVM Cpu0 新后端6

2024-06-10 04:20
文章标签 llvm cpu0 新后

本文主要是介绍LLVM Cpu0 新后端6,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

LLVM 后端实践笔记

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

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

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

目录

一、修改的文件

1.1 Cpu0ISelDAGToDAG.cpp

1.2 Cpu0ISelLowering.cpp/.h

1.3 Cpu0InstrInfo.td

1.4 Cpu0SEISelDAGToDAG.cpp/.h

1.5 MCTargetDesc/Cpu0InstPrinter.cpp/.h

二、实现结果

2.1 局部指针

2.2 char类型

2.3 bool类型

2.4 short

2.5 long long 类型

2.6 局部数组、结构体

2.7 全局数组、结构体

2.8 向量

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 局部指针

int test_local_pointer() {int b = 3;int *p = &b;return *p;
}
	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类型

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;
}
	addiu	$sp, $sp, -24lui	$2, %got_hi(b)addu	$2, $2, $gpld	$2, %got_lo(b)($2)  # 与上边搭配加载全局变量b的首地址lbu	$3, 1($2)           # 计算b[1]的地址,存到寄存器3sb	$3, 20($sp)         # 将寄存器3内的地址存到栈上lbu	$2, 1($2)           # 再次计算b[1]的地址,存到寄存器2sb	$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到 date1lhu	$3, 2($2)           # 这里是相同的逻辑lhu	$2, 0($2)shl	$2, $2, 16or	$2, $2, $3st	$2, 8($sp)          # 将这4字节存到栈上,也就是存放year, month, day到 date1lbu	$2, 10($sp)         # 从偏移10的位置读出date1.month(我们知道year, month, day在偏移8的位置,year两个字节,因此month在偏移10的位置,这里很正确)sb	$2, 4($sp)          # 将其存到栈上(m)lbu	$2, 14($sp)         # 从偏移14的位置读出date1.secondsb	$2, 0($sp)          # 将其存到栈上(s)addiu	$2, $zero, 0addiu	$sp, $sp, 24ret	$lr

2.3 bool类型

bool test_load_bool()
{int a = 1;if (a < 0)return false;return true;
}

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

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

2.4 short

int test_signed_char()
{char a = 0x80;int i = (signed int)a;i = i + 2; // i = (-128 + 2) = -126return i;
}int test_unsigned_char()
{unsigned char c = 0x80;unsigned int ui = (unsigned int)c;ui = ui + 2; // ui = (128 + 2) = 130return (int)ui;
}int test_signed_short()
{short a = 0x8000;int i = (signed int)a;i = i + 2; // i = (-32768 + 2) = -32766return i;
}int test_unsigned_short()
{unsigned short c = 0x8000;unsigned int ui = (unsigned int)c;ui = ui + 2; // ui = (32768 + 2) = 32770return (int)ui;
}
st_signed_short
...addiu	$sp, $sp, -8ori	$2, $zero, 32768sh	$2, 4($sp)lh	$2, 4($sp)st	$2, 0($sp)ld	$2, 0($sp)addiu	$2, $2, 2st	$2, 0($sp)ld	$2, 0($sp)addiu	$sp, $sp, 8ret	$lr
...
test_unsigned_short:
...addiu	$sp, $sp, -8ori	$2, $zero, 32768sh	$2, 4($sp)lhu	$2, 4($sp)st	$2, 0($sp)ld	$2, 0($sp)addiu	$2, $2, 2st	$2, 0($sp)ld	$2, 0($sp)addiu	$sp, $sp, 8ret	$lr
...

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

2.5 long long 类型

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,00000003long long d = a - b;    // d = 0x00000002,00000001long long e = a * b;    // e = 0x00000005,00000002long long f = (long long)a1 * (long long)b1;    // f = 0x00060050,01000000return (c+d+e+f);       // (0x0006005b,01000006) = (393307,16777222)
}
	addiu	$sp, $sp, -56addiu	$2, $zero, 2st	$2, 52($sp)          # a的低位addiu	$2, $zero, 3st	$2, 48($sp)          # a的高位addiu	$2, $zero, 1st	$2, 44($sp)          # b的低位st	$2, 40($sp)          # b的高位lui	$2, 12289st	$2, 36($sp)          # a1lui	$2, 8193st	$2, 32($sp)          # b1ld	$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, $5subu	$2, $2, $4subu	$2, $2, $6subu	$3, $3, $5st	$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, $5multu	$3, $4mflo	$3mfhi	$6addu	$5, $6, $5mul	$2, $2, $4addu	$2, $5, $2st	$3, 12($sp)st	$2, 8($sp)ld	$2, 36($sp)ld	$3, 32($sp)mult	$2, $3mflo	$2mfhi	$3st	$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, $5addu	$4, $2, $4sltu	$2, $4, $2addu	$2, $3, $2ld	$3, 8($sp)ld	$5, 12($sp)addu	$5, $4, $5sltu	$4, $5, $4addu	$2, $2, $3addu	$2, $2, $4ld	$3, 4($sp)ld	$4, 0($sp)addu	$2, $2, $4addu	$3, $5, $3sltu	$4, $3, $5addu	$2, $2, $4addiu	$sp, $sp, 56ret	$lr

2.6 局部数组、结构体

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

2.7 全局数组、结构体

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
}
	addiu	$sp, $sp, -8lui	$2, %got_hi(date)addu	$2, $2, $gpld	$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, $gpld	$2, %got_lo(a)($2)ld	$2, 4($2)st	$2, 0($sp)ld	$2, 0($sp)ld	$3, 4($sp)addu	$2, $2, $3addiu	$sp, $sp, 8ret	$lr

2.8 向量

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函数的汇编,我们看个稍微短一点的:

	addiu	$sp, $sp, -64st	$10, 60($sp)                    # 4-byte Folded Spillst	$9, 56($sp)                     # 4-byte Folded Spilladdiu	$2, $zero, 3st	$2, 44($sp)addiu	$3, $zero, 2st	$3, 40($sp)addiu	$2, $zero, 1st	$2, 36($sp)addiu	$2, $zero, 0st	$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, $10subu	$6, $2, $6slt	$5, $5, $9subu	$5, $2, $5slt	$4, $4, $8subu	$4, $2, $4slt	$3, $3, $7subu	$2, $2, $3st	$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, $3ld	$3, 12($sp)ld	$3, 4($sp)ld	$3, 0($sp)ld	$3, 8($sp)addu	$2, $2, $3ld	$3, 8($sp)ld	$3, 4($sp)ld	$3, 0($sp)ld	$3, 12($sp)addu	$2, $2, $3ld	$9, 56($sp)                     # 4-byte Folded Reloadld	$10, 60($sp)                    # 4-byte Folded Reloadaddiu	$sp, $sp, 64ret	$lr

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

2.9 cl指令

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

这篇关于LLVM Cpu0 新后端6的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1047196

相关文章

LLVM入门2:如何基于自己的代码生成IR-LLVM IR code generation实例介绍

概述 本节将通过一个简单的例子来介绍如何生成llvm IR,以Kaleidoscope IR中的例子为例,我们基于LLVM接口构建一个简单的编译器,实现简单的语句解析并转化为LLVM IR,生成对应的LLVM IR部分,代码如下,文件名为toy.cpp,先给出代码,后面会详细介绍每一步分代码: #include "llvm/ADT/APFloat.h"#include "llvm/ADT/S

LLVM IR指令VM混淆分析

未混淆编译  编写一个最简单的测试代码,对 test_add函数用于对两个数相加: int __attribute((__annotate__("vm"))) test_add(int a, int b) {int c = a + b;return c;}int main(void) {int c = test_add(1, 2);return c;} 编译成中间代码:  未加

Windows编译Hikari-LLVM15[llvm-18.1.8rel]并集成到Android Studio NDK

Windows编译Hikari-LLVM15[llvm-18.1.8rel]并集成到Android Studio NDK 工具1、w64devkit2、ndk3、cmake 编译1、准备工作2、开始编译 集成1、替换文件2、使用 工具 1、w64devkit w64devkit 解压出来给个环境变量 验证一下 2、ndk 通过android studio安装 nd

VS2022使用指定的LLVM版本

LLVM下载地址:Releases · llvm/llvm-project · GitHub LLVM/Clang toolsets for Visual Studio 2022, 2019, 2017, 2015, 2013, 2012 and 2010. GitHub - zufuliu/llvm-utils: LLVM/Clang toolsets for Visual Studio 2

简述 LLVM 与 Clang 及其关系

随着 Android P 的逐步应用,越来越多的客户要求编译库时用 libc++ 来代替 libstdc++。libc++ 和 libstdc++ 这两个库有关系呢?它们两个都是 C++ 标准库,libc++ 是针对 Clang 编译器特别重写的 C++ 标准库,而 libstdc++ 则是 GCC 的对应 C++ 标准库了。从 Android 市场来说,Android NDK 已在具体应用中放弃

llvm windows编译成功

一、所需工具 Visual Studio 推荐版本:Visual Studio 2022。其他版本亦可支持。 CMake 下载地址 Ninja 下载地址 LLVM 版本参考:llvm-project-llvmorg-18.1.8下载地址 二、配置与编译步骤 以管理员身份打开命令行终端,输入以下命令来设置执行策略: Set-ExecutionPolicy Unrestricted

LLVM——安装多版本LLVM和Clang并切换使用(Ubuntu)

1、描述 本机(Ubuntu22)已经安装了LLVM-14,但是需要使用LLVM-12。安装LLVM-12和Clang-12并切换使用。 2、过程 安装LLVM-12和Clang-12。 sudo apt-get install llvm-12sudo apt-get install clang-12 【注】运行 sudo apt-get install llvm-12 命令时,默认情况

【LLVM】‘ffast-math’ and ‘ffp-contract’

最近看到一个issue,修改的核心代码部分并不多,可以参考此处的介绍以及此处的issue。 看起来关键就是判断-ffp-contract会将contract的值设为最后一个此选项的值,否则的话,如果只指定了-ffast-math但是没有通过-ffp-contract设置值,就会将FPContract设置成on。 用伪代码表示: if(option == -ffp-contract) {FPCon

LLVM 中 的 pass 及其管理机制

概述 LLVM 编译器框架的核心概念是任务调用和执行 编译器开发者将IR分解为不同的处理对象,并将其处理过程实现为单独的pass类型。在编译器初始化,pass被实例化,并被添加到pass管理中 pass 管理器(pass manager) 以流水线的方式将各个独立的pass 衔接起来,然后以预定义顺序遍历每个pass,根据pass实例返回值启动、停止或重复运行不同的pass 因此,LLVM

iOS底层探索(一) - 从零开始认识Clang与LLVM

入门起步 从编译器说起 为什么需要编译? 大家都知道,我们的计算机CPU只能读懂机器码(machine code,也就是由一堆0和1组成的编码);但我们现在编写的代码并不是机器码,而是高级编程语言(Objective-C、Swift、Java、...),最终也可以被计算机所执行,这就需要编译了,在编译的过程中,编译器的作用便是把我们的高级编程语言通过一系列的操作转化成可被计算机执行的机器