15

我有这个非常简单的代码:

#include <stdio.h>
#include <math.h>
int main()
{
    long v = 35;
    double app = (double)v;
    app /= 100;
    app = log10(app);
    printf("Calculated log10 %lf\n", app);
    return 0;
}

此代码在 x86 上完美运行,但在 arm 上不起作用,结果为 0.00000。一些想法?

其他信息:

操作系统:linux 3.2.27

我用 ct-ng 构建 arm 工具链:arm-unknown-linux-gnueabi-

libc 版本 2.13

输出gcc -v

使用内置规范。COLLECT_GCC=arm-unknown-linux-gnueabi-gcc COLLECT_LTO_WRAPPER=/opt/x-tools/arm-unknown-linux-gnueabi/libexec/gcc/arm-unknown-linux-gnueabi/4.5.1/lto-wrapper 目标:arm -unknown-linux-gnueabi 配置为:/home/mirko/misc/rasppi-ct-ng-files/.build/src/gcc-4.5.1/configure --build=x86_64-build_unknown-linux-gnu --host =x86_64-build_unknown-linux-gnu --target=arm-unknown-linux-gnueabi --prefix=/opt/x-tools/arm-unknown-linux-gnueabi --with-sysroot=/opt/x-tools/ arm-unknown-linux-gnueabi/arm-unknown-linux-gnueabi//sys-root --enable-languages=c --disable-multilib --with-pkgversion=crosstool-NG-1.9.3 --enable-__cxa_atexit --disable-libmudflap --disable-libgomp --disable-libssp --with-host-libstdcxx='-static-libgcc -Wl,-Bstatic,-lstdc++,-Bdynamic -lm' --with-gmp=/home /mirko/misc/rasppi-ct-ng-files/.

4

1 回答 1

10

ARM Linux 发行版上的浮点支持并非微不足道。因此,您应该使用与您的系统相匹配的工具链,即操作系统和硬件,并使用正确的编译开关。

首先,您需要了解 ARM 的调用约定,即“调用函数时如何传递参数?” . ARM 是 RISC 架构,只能在寄存器上工作。没有直接操作内存的指令。如果您需要更改内存中的值,您首先需要将其加载到寄存器中,对其进行修改,然后您需要将其存储回内存中。

当您调用一个函数时,您可能需要向它传递参数,您可以将参数放在堆栈(内存)上,但由于 ARM 只能使用寄存器,因此您的函数可能首先会将它们加载回寄存器。为了避免这种浪费,ARM 调用约定使用寄存器来传递参数。但是,由于 ARM 的寄存器数量有限,调用约定还要求您仅将前四个 (r0-r3) 寄存器用于前四个参数,其余的仍必须使用堆栈来传递。

第二件事是早期的 ARM 内核没有任何浮点支持,操作是在软件中实现的。(这仍然是通过 gcc 支持的-mfloat-abi=soft。)

我们可以通过以下代码段轻松演示这意味着什么。

float pi2(float a) {
    return a * 3.14f;
}

编译这个 via-c -O3 -mfloat-abi=softobdumping 给了我们

00000000 <pi2>:
   0:   f24f 51c3   movw    r1, #62915  ; 0xf5c3
   4:   b508        push    {r3, lr}
   6:   f2c4 0148   movt    r1, #16456  ; 0x4048
   a:   f7ff fffe   bl  0 <__aeabi_fmul>
   e:   bd08        pop {r3, pc}

正如您所看到的(实际上它是不可见的:))pi2获取它的参数r0,填充pi constantr1使用__aeabi_fmul将它们相乘并返回结果r0。由于__aeabi_fmul也使用相同的调用约定,有关的详细信息r0不可见。我们所有的函数都是为了填充r1和委托给__aeabi_fmul.

当向 ARM 添加浮动硬件支持时(同样是因为架构风格),它带有自己的一组寄存器(s0, s1, ...)

如果我们编译相同的代码片段-c -O3 -mfloat-abi=softfp并转储我们得到

00000000 <pi2>:
   0:   eddf 7a04   vldr    s15, [pc, #16]  ; 14 <pi2+0x14>
   4:   ee07 0a10   vmov    s14, r0
   8:   ee27 7a27   vmul.f32    s14, s14, s15
   c:   ee17 0a10   vmov    r0, s14
  10:   4770        bx  lr
  12:   bf00        nop
  14:   4048f5c3    .word   0x4048f5c3

正如您现在所看到的,编译器不会创建对的调用,__aeabi_fmul而是vmul.f32在将位于的参数移动r0s14并填充3.14到后创建一条指令s15。在乘法指令之后,它将可用的结果移s14回,r0 因为该函数的任何调用者都会因为调用约定而期望它

现在,如果您将pi2其视为某个第三方提供给您的库,您可以理解 soft 和 softfp 实现都为您做同样的事情,您可以互换使用它们。如果系统为您提供它们,那么您不会关心您的应用程序是否在支持硬件浮点的系统上运行。这对于让旧软件在新硬件上运行非常好。

然而,在保持兼容性的同时,这种方法引入了在 ARM 寄存器和 FP 寄存器之间移动值的开销。这显然会影响性能,并由一个新的调用约定解决,hardgcc. 这个新约定规定,如果您的函数中有浮点参数,您可以使用与普通寄存器交错的浮点寄存器,也可以在浮点寄存器中返回浮点值s0

再次,如果我们编译我们的代码片段-c -O3 -mfloat-abi=hard并转储我们得到

00000000 <pi2>:
   0:   eddf 7a02   vldr    s15, [pc, #8]   ; c <pi2+0xc>
   4:   ee20 0a27   vmul.f32    s0, s0, s15
   8:   4770        bx  lr
   a:   bf00        nop
   c:   4048f5c3    .word   0x4048f5c3

你可以看到没有寄存器被移动。pi2传入的参数s0,编译器创建代码以填充3.14s15用于vmul.f32 s0, s0, s15获得我们想要的结果s0

这种新约定的大问题是,当您改进编译器生成的代码时,您会完全扼杀兼容性。您不能指望使用hard约定构建的应用程序可以与为 softfp 构建的库一起使用,soft/softfp而为 softfp 构建的应用程序不会与为 hard 构建的库一起使用。

有关调用约定的更多信息,您应该查看ARM 的网站

于 2012-12-20T15:13:10.583 回答