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

时间:2011-12-03 00:32:08

标签: gcc arm cortex-m3 codewarrior

我正在使用飞思卡尔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指令更有意义,但这没有任何不同。

2 个答案:

答案 0 :(得分:6)

优化该代码所带来的问题是:

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(well 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);
}

给出了这个

GCC

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

GCC

08000340 <mul_test04>:
 8000340:   fb81 0100   smull   r0, r1, r1, r0
 8000344:   4608        mov r0, r1
 8000346:   4770        bx  lr

我可以看到0x40000000被认为是一个浮点数,你跟踪小数位,那个地方是一个固定的位置。 0x20000000作为答案是有意义的。我还无法确定31位移位是否普遍适用于这种情况。

上面使用的完整示例是

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都做了我正在描述的内容,将函数的运行时代码保留为全局函数,但是在文件中它在编译时计算了答案并将硬编码的答案放在代码中而不是调用函数: / p>

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或以其他方式使用条件编译选项来构建不带汇编程序的不同目标。

答案 1 :(得分:-1)

GCC支持实际的定点类型:http://gcc.gnu.org/onlinedocs/gcc/Fixed_002dPoint.html

我不确定它会使用什么指令,但它可能会让你的生活更轻松。