gcc - 使用 ARM Cortex-M4 和 gcc 编译器进行定点数学运算

标签 gcc arm cortex-m3 codewarrior

我正在使用 Freescale Kinetis K60 和 CodeWarrior IDE(我相信它使用 GCC 作为编译器)。

我想将两个 32 位数字相乘(产生一个 64 位数字)并且只保留高 32 位。

我认为 ARM Cortex-M4 的正确汇编指令是 SMMUL 指令。我更愿意从 C 代码而不是汇编访问此指令。我该怎么做呢?

我想代码最好是这样的:

int a,b,c;

a = 1073741824;   // 0x40000000 = 0.5 as a D0 fixed point number
b = 1073741824;   // 0x40000000 = 0.5 as a D0 fixed point number

c = ((long long)a*b) >> 31;  // 31 because there are two sign bits after the multiplication
                             // so I can throw away the most significant bit

当我在 CodeWarrior 中尝试这个时,我得到了 c 的正确结果(536870912 = 0.25 作为 D0 FP 数字)。我在任何地方都没有看到 SMMUL 指令,乘法是 3 条指令(UMULL、MLA 和 MLA——我不明白为什么它使用无符号乘法,但这是另一个问题)。我还尝试了 32 的右移,因为这对于 SMMUL 指令可能更有意义,但这并没有什么不同。

最佳答案

优化该代码时遇到的问题是:

08000328 <mul_test01>:
 8000328:   f04f 5000   mov.w   r0, #536870912  ; 0x20000000
 800032c:   4770        bx  lr
 800032e:   bf00        nop

您的代码在运行时不做任何事情,因此优化器可以计算最终答案。

这个:
.thumb_func
.globl mul_test02
mul_test02:
    smull r2,r3,r0,r1
    mov r0,r3
    bx lr

用这个调用:
c = mul_test02(0x40000000,0x40000000);

给出 0x10000000

UMULL 给出相同的结果,因为您使用的是正数,操作数和结果都是正数,因此它不会进入有符号/无符号差异。

嗯,你让我知道了这一点。我会读你的代码,告诉编译器将乘法提升到 64 位。 smull 是两个 32 位操作数,给出 64 位结果,这不是您的代码所要求的......但无论如何 gcc 和 clang 都使用了 smull,即使我将它作为未调用的函数保留,所以它不知道编译时操作数没有超过 32 的有效数字,他们仍然使用 smull。

也许转变是原因。

是的,就是这样..
int mul_test04 ( int a, int b )
{
    int c;
    c = ((long long)a*b) >> 31; 
    return(c);
}



gcc 和 clang(以及 clang 回收 r0 和 r1 而不是使用 r2 和 r3)
08000340 <mul_test04>:
 8000340:   fb81 2300   smull   r2, r3, r1, r0
 8000344:   0fd0        lsrs    r0, r2, #31
 8000346:   ea40 0043   orr.w   r0, r0, r3, lsl #1
 800034a:   4770        bx  lr

但是这个
int mul_test04 ( int a, int b )
{
    int c;
    c = ((long long)a*b); 
    return(c);
}

给这个

海湾合作委员会:
08000340 <mul_test04>:
 8000340:   fb00 f001   mul.w   r0, r0, r1
 8000344:   4770        bx  lr
 8000346:   bf00        nop

铛:
0800048c <mul_test04>:
 800048c:   4348        muls    r0, r1
 800048e:   4770        bx  lr

因此,通过位移位,编译器意识到您只对结果的上半部分感兴趣,因此他们可以丢弃操作数的上半部分,这意味着可以使用 smull。

现在如果你这样做:
int mul_test04 ( int a, int b )
{
    int c;
    c = ((long long)a*b) >> 32; 
    return(c);
}

两个编译器都变得更聪明了,尤其是 clang:
0800048c <mul_test04>:
 800048c:   fb81 1000   smull   r1, r0, r1, r0
 8000490:   4770        bx  lr

海湾合作委员会:
08000340 <mul_test04>:
 8000340:   fb81 0100   smull   r0, r1, r1, r0
 8000344:   4608        mov r0, r1
 8000346:   4770        bx  lr

我可以看到 0x40000000 被视为一个浮点数,您可以在其中跟踪小数位,而该位置是一个固定位置。 0x20000000 作为答案是有意义的。我还不能决定 31 位移位是普遍适用还是仅适用于这种情况。

用于上述的完整示例是here

https://github.com/dwelch67/stm32vld/tree/master/stm32f4d/sample01

我确实在 stm32f4 上运行它以验证它的工作原理和结果。

编辑:

如果您将参数传递给函数而不是在函数中对其进行硬编码:
int myfun ( int a, int b )
{
     return(a+b);
}

编译器被迫生成运行时代码,而不是在编译时优化答案。

现在,如果您从另一个具有硬编码数字的函数调用该函数:
...
c=myfun(0x1234,0x5678);
...

在这个调用函数中,编译器可以选择计算答案并在编译时将它放在那里。如果 myfun() 函数是全局的(未声明为静态的),编译器不知道稍后要链接的其他代码是否会使用它,因此即使在此文件中的调用点附近,它也会优化答案,它仍然必须生成实际函数并将其保留在对象中以供其他文件中的其他代码调用,因此您仍然可以检查编译器/优化器对该 C 代码执行的操作。例如,除非您使用 llvm 来优化整个项目(跨文件),否则调用此函数的外部代码将使用真实函数而不是编译时计算的答案。

gcc 和 clang 都做了我所描述的,将函数的运行时代码作为全局函数,但在文件中它在编译时计算了答案并将硬编码的答案放在代码中而不是调用函数:
int mul_test04 ( int a, int b )
{
    int c;
    c = ((long long)a*b) >> 31;
    return(c);
}

在同一文件中的另一个函数中:
hexstring(mul_test04(0x40000000,0x40000000),1);

函数本身在代码中实现:
0800048c <mul_test04>:
 800048c:   fb81 1000   smull   r1, r0, r1, r0
 8000490:   0fc9        lsrs    r1, r1, #31
 8000492:   ea41 0040   orr.w   r0, r1, r0, lsl #1
 8000496:   4770        bx  lr

但是在所谓的地方,他们对答案进行了硬编码,因为他们拥有这样做所需的所有信息:
 8000520:   f04f 5000   mov.w   r0, #536870912  ; 0x20000000
 8000524:   2101        movs    r1, #1
 8000526:   f7ff fe73   bl  8000210 <hexstring>

如果您不想要硬编码的答案,则需要使用不在同一个优化过程中的函数。

操纵编译器和优化器归结为大量实践,它不是一门精确的科学,因为编译器和优化器在不断发展(无论好坏)。
通过隔离一个函数中的一小部分代码,你会以另一种方式导致问题,较大的函数更有可能需要一个堆栈框架,并在执行过程中将变量从寄存器驱逐到堆栈,较小的函数可能不需要这样做,并且优化器可能会因此改变代码的实现方式。您以一种方式测试代码片段以查看编译器在做什么,然后在更大的函数中使用它并且没有得到您想要的结果。如果有您想要实现的确切指令或指令序列....在汇编程序中实现它们。如果您的目标是特定指令集/处理器中的特定指令集,请避免玩游戏,避免在更改计算机/编译器/等时更改代码,而只需将汇编程序用于该目标。如果需要 ifdef 或以其他方式使用条件编译选项在没有汇编程序的情况下为不同的目标构建。

关于gcc - 使用 ARM Cortex-M4 和 gcc 编译器进行定点数学运算,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8364420/

相关文章:

c - 使用复合文字初始化变量

c - 在源代码中部分禁用 gcc 中的迂腐警告

arm - 如何检查应用程序的 cortex m3 SRAM 使用情况

c - 当我们用完 Cortex M3 上的内存时会发生什么

c - 32 位处理器上的 64 位/64 位余数查找算法?

Java 调用由 GCC 编译的 C 库有效,但在使用 G++ 编译时失败

architecture - 如何为 32 位和 64 位创建单个 makefile?

udp - NTP 请求包

使用 OpenOCD 的 ARM LPC1768 的 Eclipse GDB "init"和 "run"设置?

c - 如何在 cortex m4 中配置 MPU 寄存器?