0%

ARM浮点数翻译心得

ARM浮点数

ARM浮点数翻译心得

编译原理实现的Sysy编译器支持使用float浮点数。这里讲一下arm汇编代码生成浮点数
部分遇到的一些问题和心得。

把重要的东西放到前边来说,下边的文章可以一点不看。godbolt这个网站真的强烈推荐用于写编译器时做个参考。长下图这样,在左边写代码,右边几乎可以实时出LLVM IR代码或者汇编代码。还有很多种类和版本的编译器可以选择,在不知道该怎么翻译时这个网站可以作为一个不错的参考。主要就是方便,其实我们也完全可以在本机编译,但是比较麻烦,这个网站好处就是快,方便,编译器种类多,好看

浮点数常量的表示

首先就是浮点字面值常量的表示了。以下边这一段代码为例:

1
2
3
4
5
float pi = 3.14;
int main() {
float r = 10.0;
putfloat(pi * r * r);
}

可以看到,出现了两个浮点数字面值常量,分别是3.14和10.0。其中一个赋值给全局变量,一个赋值给局部变量,但是它俩的表示方式其实是一样的。在arm汇编中我们应该怎么表示它们呢?比较直接的想法就是下边这样,直接把3.14给糊上去不就行啦?但是其实这样是不对的,汇编的时候会报错。

1
2
3
4
5
.section .data
.global pi
.size pi, 4
pi:
.word 3.14

AFAIK just printing a decimal float works. If you really want a hexadecimal encoding, just reinterpret the floating-point number as an integer and print in hexadecimal; an “LLVM float” is just an IEEE float printed in hexadecimal.

这里我们借鉴的是LLVM IR代码中浮点数表示的思路,上边这段话的最后一句,LLVM浮点数其实就是一个浮点数以16进制的形式打印出来(当然,这只是一种表现形式,除了16进制之外,还可以用科学计数法之类的表示LLVM浮点数)。在arm汇编中,我们直接把所有的float字面值常量都转译成对应的32位无符号整数就行了。重点就是保持无符号整数的位模式和它对应的float值的位模式一样(遵从IEEE格式)。代码也很简单,如下所示,value存储了对应的float常量值,保持位模式不变,把它翻译成无符号整数就行,然后打印的时候就打印v的值即可。

1
2
3
4
float value = (float)dynamic_cast<ConstantSymbolEntry *>(se)->getValue();
uint32_t v = reinterpret_cast<uint32_t &>(value);
// 上边这一句也可以这样写
// uint32_t v = *((uint32_t*)&value);

根据我们的处理方式,开头代码中的float pi = 3.14;可以如下翻译:

1
2
3
4
5
.section .data
.global pi
.size pi, 4
pi:
.word 1078523331

1078523331的16进制就是4048F5C3。

上图的网站为https://tooltt.com/floatconverter/

想把浮点数常量放到某个寄存器里也是类似的,把对应的无符号整数想办法放到寄存器里就行。

软浮点和硬浮点

解决了浮点常量的表示之后我们就要考虑怎么翻译具体的浮点运算之类的指令了。这里有两种方案,一种是软浮点,就是没有显式的浮点运算指令,浮点运算纯靠调用ABI;还有一种方案是硬浮点,就是使用专用寄存器s那一套还有浮点运算指令。由于一开始arm芯片没有浮点运算单元FPU(就是个协处理器),arm早期使用普通指令来模拟浮点运算,这种方法比较慢,但是用起来简单。慢慢地随着发展就逐渐的向使用专用的浮点运算指令过渡,这种方法比较快。但是,这里就出现了一个问题,对于没有FPU的芯片,硬浮点编译的程序显然跑不起来,这就是向前不兼容?那我有FPU,你总不能不让我用把,于是就出现了一个中间态,还是用的软浮点的一些规定和接口,但是软浮点里面ABI之类的可以用专门的浮点指令来实现。

扯得有点远了,想了解更多上网搜,一搜一大堆,比如这个。这里我们只要明确,翻译浮点汇编代码时,有两种不同的方式,一种是软浮点,它的感觉就是要浮点运算就调用对应的函数,不需要其他的专用寄存器啥的;另一种是硬浮点,它的感觉就是为浮点运算再量身打造一套寄存器和指令,浮点运算就使用专门的寄存器和指令。下边会举几个例子来进行大致的说明。

对了,还有一点就是用arm-linux-gnueabihf-gcc默认是硬浮点架构,arm-linux-gnueabi-gcc默认是软浮点架构,这里在编译libsysy.a这个库的时候要注意,不然链接的时候会出错。hf=hard float?

软浮点实现

下边来举几个例子切身感受一下软浮点的风格。

1
2
3
4
5
float a, b, c; // 这里就不赋初值了
a = b + c;
a = b - c;
a = b * c;
a = b / c;

对于上边这段代码,软浮点咋翻译嘞?如下所示:(手翻的,意思对,但不一定一点问题都没)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
sub sp, sp, #12 ; 开栈空间

; a = b + c;
ldr r0, [fp, #-8] ;
ldr r1, [fp, #-12] ; 取出b和c
bl __aeabi_fadd ; 这个就是ABI,就是调库,就跟调用普通函数一样
str r0, [fp, #-4]

; a = b - c;
ldr r0, [fp, #-8] ;
ldr r1, [fp, #-12] ;
bl __aeabi_fsub ;
str r0, [fp, #-4]

; a = b * c;
ldr r0, [fp, #-8] ;
ldr r1, [fp, #-12] ;
bl __aeabi_fmul ;
str r0, [fp, #-4]

; a = b / c;
ldr r0, [fp, #-8] ;
ldr r1, [fp, #-12] ;
bl __aeabi_fdiv ;
str r0, [fp, #-4]

通过上述样例,应该很好找到软浮点的感觉,其实就是调函数。下边就没啥好说的了,这里再列出几个翻译时候会用到的函数吧。

function note
__aeabi_fadd 浮点加
__aeabi_fsub 浮点减
__aeabi_fmul 浮点乘
__aeabi_fdiv 浮点除
__aeabi_fcmpeq 浮点比较,==
__aeabi_fcmpge 浮点比较,>=
__aeabi_fcmplt 浮点比较,<
__aeabi_fcmple 浮点比较,<=
__aeabi_fcmpgt 浮点比较,>
__aeabi_f2iz 浮点转整型
__aeabi_i2f 整型转浮点

硬浮点实现

至于硬浮点,就要用一套新的寄存器了s0-s31。

还是翻译上边的代码,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
sub sp, sp, #12 ; 开栈空间

; a = b + c
vldr.32 s16, [sp, #-8]
vldr.32 s17, [sp, #-12]
vadd.f32 s18, s16, s17
vstr.32 s18, [sp, #-4]

; a = b - c
vldr.32 s16, [sp, #-8]
vldr.32 s17, [sp, #-12]
vsub.f32 s18, s16, s17
vstr.32 s18, [sp, #-4]

; a = b * c
vldr.32 s16, [sp, #-8]
vldr.32 s17, [sp, #-12]
vmul.f32 s18, s16, s17
vstr.32 s18, [sp, #-4]

; a = b / c
vldr.32 s16, [sp, #-8]
vldr.32 s17, [sp, #-12]
vdiv.f32 s18, s16, s17
vstr.32 s18, [sp, #-4]

可以看到,不再是调用函数了,浮点运算都有了自己的指令。想了解更多硬浮点的指令,可以去arm官网上看https://developer.arm.com/documentation/den0018/a/NEON-and-VFP-Instruction-Summary/List-of-all-NEON-and-VFP-instructions?lang=en

实现硬浮点,要改的代码更多。

ARM中的栈指针对齐

按照约定,ARM中的堆栈指针要八字节对齐,否则在处理浮点数的时候可能会出现比较奇怪的错误。一个参考网站。这里说明我们栈指针对齐的方法。先把栈指针逻辑右移三位,再把它逻辑左移三位,这样就8字节对齐了。

1
2
lsr sp, sp, #3
lsl sp, sp, #3

ARM中函数调用中浮点数和整数混合传参

如果函数参数中既有整数又有浮点数,那么它们互不相干。也就是说,整数用r0-r3寄存器传递,浮点数用s0-s15寄存器传递。有可能有整数寄存器不够用了需要用栈传参,而此时浮点数s寄存器还够用的情况。可以看官网