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

8
我正在使用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指令更有意义,但是结果并没有任何不同。

你是否开启了编译优化?如果你的编译器没有生成最优代码,你可以编写一个小的汇编函数或者使用一些来自C语言的内联汇编。 - TJD
2个回答

7

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

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

给出这个

gcc:

08000340 <mul_test04>:
 8000340:   fb00 f001   mul.w   r0, r0, r1
 8000344:   4770        bx  lr
 8000346:   bf00        nop

clang:

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都做了我所描述的事情,将运行时代码作为全局函数留在函数中,但是在文件内部,它在编译时计算答案并将硬编码的答案放入代码中而不是调用函数。
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或其他操作以构建不同目标的代码。

我使用了目前所称的CodeSourcery GCC编译器以及来自LLVM 3.0版本发布的Clang编译器。 - old_timer
2
看起来我错了。CodeWarrior似乎没有使用GCC。它引用了一个名为mwccarm的可执行文件,这似乎是Freescale特定的编译器。我已经尝试将乘法定义为函数,但仍然无法使编译器使用smull命令--实际上umull命令仍然存在。我将尝试下载gcc或clang并查看会发生什么。 - EpicAdv
1
我认为我没有成功地告诉编译器编译一个独立的函数。它似乎在回顾它被调用的地方(和是否调用)并进行优化。我该如何强制它像你在这里做的那样编译通用函数? - EpicAdv
那可能是你正在使用的元编译器 http://www.dwelch.com/gba/dhry.htm 我曾经在早期尝试找到一个好的Thumb编译器时,混合了一些metaware和其他的评估版本。 - old_timer
1
谢谢。我在这里学到了不少东西。 1) 我正在查看整个编译项目的反汇编窗口。 显然,如果它从不调用该函数,则不会包含在最终链接版本中。 如果我只查看函数的反汇编代码,我可以看到正在生成什么。 2) 对于这个编译器,似乎它不使用smmul命令,而是单独进行乘法和移位(muls,asr,lsr和orr)。 我已经使用汇编指令测试了smmul,并且它按照我想要的方式工作(除了32位移位而不是31位)。 从C语言,我能强制它使用smmul吗? - EpicAdv
我认为问题在于说服编译器这些确实是有符号数。如果你已经硬编码了数字,例如0x40000000自乘,有符号或无符号都可以工作。long long是否正确地将int符号扩展?也许这与此有关... - old_timer

-1

1
从链接页面中可以看到:“并非所有目标都支持定点类型。”根据我的经验,很少有目标支持定点类型。 - user1619508

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接