GCC浮点相关的编译选项配置

GCC浮点相关的编译选项配置

gcc的浮点相关编译选项配置

GCC 中的浮点相关的编译选项属于依赖于不同平台的配置选项, 本文的相关配置都是在 ARM32 平台下. SylixOS 下常用的浮点编译配置有两个 -mfpu=name, -mfloat-abi=name

测试程序

#include <stdio.h>
#include <time.h>

float calc(float *src_mem_32, float *dst_mem_32)
{
   float mem_32;
   int i, j;

   for (j = 0; j < 1024; j++) {
       for (i = 0; i < 1024; i++) {
           mem_32 = src_mem_32[i] + dst_mem_32[i];
      }
  }

   return mem_32;
}

int main (int argc, char **argv)
{
   float src_mem_32[1024] = {1.024};
   float dst_mem_32[1024] = {0.933};
   float mem_32;
   int i, j;

   ULONG clockTick = lib_clock();

   for (j = 0; j < 1024; j++) {
       for (i = 0; i < 1024; i++) {
           mem_32 = src_mem_32[i] + dst_mem_32[i];
      }
  }

   mem_32 = calc(src_mem_32, dst_mem_32);

   clockTick = lib_clock() - clockTick;
   printf("clockTick = %u ms\r\n", (unsigned int)clockTick);

   return (0);
}

通过一个计算浮点数的程序来测试浮点配置的运行情况.

浮点编译选项

-mfpu=name

SylixOS 中支持配置的浮点编译选项如图所示, 根据使用的芯片配置对应的 FPU 选择对应的浮点处理器类型, 不同的浮点处理器类型有着不同的指令, CPU 会将这些浮点指令不能识别之后交给 FPU 进行处理从而使用 FPU 的浮点加速.

通过将 base 工程中的 config.mk 的 FPU_TYPE 配置 SylixOS 工程的编译选项, 浮点的配置只有在 app 工程以及动态库, 静态库工程才有效.通过设置 FPU_TYPE := disable 不使用浮点. 编译上面所示的测试程序, 程序在编译时没有任何的浮点相关编译选项.执行程序输出为:

[root@sylixos:/apps/epics]# /apps/fpuTest/fpuTest
clockTick = 102 ms

程序运行了 102 ms, 通过 objdump 查看编译的结果, 从 calc 函数的代码段可以找到

00000304 <calc>:
304:   e92d4800       push   {fp, lr}
308:   e28db004       add     fp, sp, #4
30c:   e24dd018       sub     sp, sp, #24
310:   e50b0018       str     r0, [fp, #-24]
314:   e50b101c       str     r1, [fp, #-28]
318:   e3a03000       mov     r3, #0
31c:   e50b3010       str     r3, [fp, #-16]
320:   ea00001a       b       390 <calc+0x8c>
324:   e3a03000       mov     r3, #0
328:   e50b300c       str     r3, [fp, #-12]
32c:   ea000011       b       378 <calc+0x74>
330:   e51b300c       ldr     r3, [fp, #-12]
334:   e1a03103       lsl     r3, r3, #2
338:   e51b2018       ldr     r2, [fp, #-24]
33c:   e0823003       add     r3, r2, r3
340:   e5931000       ldr     r1, [r3]
344:   e51b300c       ldr     r3, [fp, #-12]
348:   e1a03103       lsl     r3, r3, #2
34c:   e51b201c       ldr     r2, [fp, #-28]
350:   e0823003       add     r3, r2, r3
354:   e5933000       ldr     r3, [r3]
358:   e1a00001       mov     r0, r1
35c:   e1a01003       mov     r1, r3
360:   eb000078       bl     548 <__addsf3>
364:   e1a03000       mov     r3, r0
368:   e50b3008       str     r3, [fp, #-8]
36c:   e51b300c       ldr     r3, [fp, #-12]
370:   e2833001       add     r3, r3, #1
374:   e50b300c       str     r3, [fp, #-12]
378:   e51b300c       ldr     r3, [fp, #-12]
37c:   e3530b01       cmp     r3, #1024       ; 0x400
380:   baffffea       blt     330 <calc+0x2c>
384:   e51b3010       ldr     r3, [fp, #-16]
388:   e2833001       add     r3, r3, #1
38c:   e50b3010       str     r3, [fp, #-16]
390:   e51b3010       ldr     r3, [fp, #-16]
394:   e3530b01       cmp     r3, #1024       ; 0x400
398:   baffffe1       blt     324 <calc+0x20>
39c:   e51b3008       ldr     r3, [fp, #-8]
3a0:   e1a00003       mov     r0, r3
3a4:   e24bd004       sub     sp, fp, #4
3a8:   e8bd8800       pop     {fp, pc}

地址 360用了 __addsf3 , 这个函数是通过非浮点运算计算出浮点数的计算结果, 将两个浮点数的和返回, 实际的软件浮点就是编译器将浮点运算替换为已经准备好的浮点运算的库函数, 且一般软件浮点的效率较低.

再通过设置 FPU_TYPE := vfpv4 开启 (Vector Floating-Point)VFP 浮点, 编译测试程序, 会发现编译过程中添加了编译选项 -mfloat-abi=softfp -mfpu=vfpv4 , 执行程序输出为

[root@sylixos:/apps/epics]# /apps/fpuTest/fpuTest
clockTick = 71 ms

程序运行了 102 ms, 通过 objdump 查看编译的结果, 从 calc 函数的代码段可以找到

00000304 <calc>:
304:   e52db004       push   {fp}           ; (str fp, [sp, #-4]!)
308:   e28db000       add     fp, sp, #0
30c:   e24dd01c       sub     sp, sp, #28
310:   e50b0018       str     r0, [fp, #-24]
314:   e50b101c       str     r1, [fp, #-28]
318:   e3a03000       mov     r3, #0
31c:   e50b3010       str     r3, [fp, #-16]
320:   ea000017       b       384 <calc+0x80>
324:   e3a03000       mov     r3, #0
328:   e50b300c       str     r3, [fp, #-12]
32c:   ea00000e       b       36c <calc+0x68>
330:   e51b300c       ldr     r3, [fp, #-12]
334:   e1a03103       lsl     r3, r3, #2
338:   e51b2018       ldr     r2, [fp, #-24]
33c:   e0823003       add     r3, r2, r3
340:   ed937a00       vldr   s14, [r3]
344:   e51b300c       ldr     r3, [fp, #-12]
348:   e1a03103       lsl     r3, r3, #2
34c:   e51b201c       ldr     r2, [fp, #-28]
350:   e0823003       add     r3, r2, r3
354:   edd37a00       vldr   s15, [r3]
358:   ee777a27       vadd.f32       s15, s14, s15
35c:   ed4b7a02       vstr   s15, [fp, #-8]
360:   e51b300c       ldr     r3, [fp, #-12]
364:   e2833001       add     r3, r3, #1
368:   e50b300c       str     r3, [fp, #-12]
36c:   e51b300c       ldr     r3, [fp, #-12]
370:   e3530b01       cmp     r3, #1024       ; 0x400
374:   baffffed       blt     330 <calc+0x2c>
378:   e51b3010       ldr     r3, [fp, #-16]
37c:   e2833001       add     r3, r3, #1
380:   e50b3010       str     r3, [fp, #-16]
384:   e51b3010       ldr     r3, [fp, #-16]
388:   e3530b01       cmp     r3, #1024       ; 0x400
38c:   baffffe4       blt     324 <calc+0x20>
390:   e51b3008       ldr     r3, [fp, #-8]
394:   e1a00003       mov     r0, r3
398:   e24bd000       sub     sp, fp, #0
39c:   e49db004       pop     {fp}           ; (ldr fp, [sp], #4)
3a0:   e12fff1e       bx     lr

地址 358 通过浮点指令 vadd.f32 来计算两个浮点寄存器 s15, s14 的和, 硬件浮点就是将浮点指令提供给 FPU 来完成对浮点数的计算, 并且计算效率对比软件浮点有着明显的提升.

-mfloat-abi=name

浮点的二进制接口, 这里的 name 可以设置为 soft, softfp and hard.

  • 如果设置为 soft 就是告诉编译器用软浮点来处理所有的浮点运算, 这样设置的结果相当于 FPU_TYPE 为 disable

  • 如果设置为 softfp 告诉编译器通过浮点指令来进行浮点运算, 但是使用软浮点的 calling conventions(这里后面会解释)

  • 如果设置为 hard 告诉编译器通过浮点指令来运算浮点运算, 且使用 FPU 的 calling conventions

在 SylixOS 系统下只要设置了 -mfpu 就会采用默认的配置 FLOAT_ABI := softfp , 如果需要指定其他的 ABI 配置在 base 工程中的 config.mk 添加相应配置即可.

调用约定(Calling Convention)是规定子过程如何获取参数以及如何返回的方案,其通常与架构、编译器等相关。具体来说,调用约定一般规定了

  • 参数、返回值、返回地址等放置的位置(寄存器、栈或存储器等)

  • 如何将调用子过程的准备工作与恢复现场的工作划分到调用者(Caller)与被调用者(Callee)身上

ARM32 架构的栈帧可以通过下图表示

当程序从调用者运行到被调用这的时候, 调用者(Caller)会保存一部分寄存器的数据在栈中, 等函数调用完成之后从栈中恢复, 这样可以保证被调用者(Callee)前后数据的传递.

void func (void)
{
int a = 5;
   int x = 10, y = 20;
   swap(&x, &y);
   
   printf("a = %d\n",a);
}

这里的变量 a 在函数 func 中保存在寄存器上, 在调用 swap 的时候, swap 函数可能会用到 变量 a 现在所在的寄存器, 导致变量 a 的值出错, 就可以通过在调用 swap 函数之前将寄存器的值先保存在栈中, 等调用 swap 结束之后在将栈中的数据恢复到寄存器确保数据的传递.

同理既然保存寄存器的操作可以放在调用(callee)之前, 同样可以放在调用 callee 函数之中, 在 callee 的真正代码之前可以保存寄存器, 在运算结束之后返回之前恢复这些寄存器.

通过上述的两种保存寄存器的方式, 人为规定了一些寄存器在 callee 函数之前保存, 而另一些寄存器在 callee 之中保存

  • r0-r3 are the argument and scratch registers; r0-r1 are also the result registers

  • r4-r8 are callee-save registers

  • r9 might be a callee-save register or not (on some variants of AAPCS it is a special register)

  • r10-r11 are callee-save registers

  • r12-r15 are special registers

现在在回头理解 softfp 和 hard 配置的区别, softfp 会继续沿用之前软件的 Calling Convention 规则, 而 hard 会通过浮点寄存器进行数据得传递和保存通过反汇编也可以观察到

左图是 hard 配置下的反汇编, 右图是 softfp 配置下的反汇编, 在 softfp 下程序沿用原本的 Calling Convnetion 通过 r0 寄存器传递返回值, 而 hard 配置下通过浮点寄存器 s0 传递返回值.

SylixOS 系统下默认都采用了 softfp, 笔者认为这样的默认配置有利于程序的兼容性, 让软浮点和硬浮点的程序接口做到了 ABI 的兼容.

参考资料

更多扩展知识见:

参考文章:编译参数浮点类型介绍

[GCC Compiler Option Summary]  https://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html 

[Arm Calling Conventions]  https://www.dyncall.org/docs/manual/manualse11.html 

[调用约定(Calling Convention)浅析]  https://zhuanlan.zhihu.com/p/111028312 

[What registers to save in the ARM C calling convention?]

https://stackoverflow.com/questions/261419/what-registers-to-save-in-the-arm-c-calling-convention 




    • Related Articles

    • 编译参数浮点类型介绍

      Q:编译参数 -mcpu=cortex-a7、-mfloat-abi=softfp 和 -mfpu=neon-vfpv4 参数选项介绍 -mfloat-abi=softfp ABI 即 “application binary interface”,即编译器将 c 代码编译成汇编代码时使用的一种规则 使用规范如下: 在编译带有浮点参数的函数时,有三种可能的编译选项: -mfloat-abi=soft -mfloat-abi=softfp -mfloat-abi=hard "soft" ...
    • SylixOS 使用 FPU 应该注意什么?

      Q: 使用 FPU 应该注意什么 SylixOS 支持浮点协处理器,每一个任务(线程有自己的 FPU 上下文,可以独立的进程浮点运算),但是, SylixOS 内核代码包括内核模块,中断函数,BSP包等等,强烈不建议使用 FPU 指令。因为没有独立的 FPU 上下文,除非操作系统启动时,参数 kfpu为 yes,这样操作系统在中断中会切换 FPU 的上下文,但是这将造成操作系统中断延迟加大。所以,除非兼容老的系统,其他情况不建议在内核和中断中使用 ...
    • GDB 调试时如何显示浮点寄存器

      Q:GDB 调试时如何显示浮点寄存器? SylixOS 的 IDE 对 64 位浮点寄存器的按 double 类型显示,如果是 float 类型的变量用 info all-reg 来查看:操作是需要选中 mips64-sylixos-elf-gdb 在 debugger console 里输入 info all-reg 或者 info float 如下图所示:                                                                     ...
    • IDE 并行编译选项配置

      Q:IDE 并行编译选项配置如何设置? 并行编译有助于提高整体代码的编译速度,节省编译源代码所需时间。RealEvo-IDE 中有两种方式可以进行并行编译配置。 1、通过 Makefile 进行配置,如: -j16 代表 16 个线程并发编译。 -jxx 能够达到多大的并行效率取决于宿主机,如:电脑 i7 8核 12线程,即使 -j40 也不能达到 40线程并发编译(实际效果受宿主机配置影响)。 ...
    • C++编译选项 -fno-rtti 和 -frtti 浅析

      问题现象:       客户在移植 C++ 程序的时候遇到了一个符号找不到的问题: 问题描述:       这个类定义在一个静态库中,静态库正常编译,在 Linux 下正常。       查看符号表确实没有这个符号,于是对比了一下编译选项,IDE 下默认是 -fno-exceptions -fno-rtti,打开 -frtti 选项后编译使用正常,对比两次的符号表。       (Makefile 中修改 C++ 编译选项)       打开 -frtti ...