我必须在C++程序中使用内联ARM汇编实现向量加法。
我写了这段代码:
#include <iostream>
#include <stdio.h>
#include <arm_neon.h>
using namespace std;
int main(){
float v1[4] = {1.0f, 2.1f, -3.1f, 2.5f};
float v2[4] = {2.0f, 1.0f, 1.1f, -2.5f};
float result[4] = { };
asm(
"ldr q31, [%[vec1]]\n"
"ldr q30, [%[vec2]]\n"
"FADD v31.4S, v31.4S, v30.4S\n"
"str q31, [%[r]]\n"
:[r]"=r"(result): [vec1]"r"(&v1), [vec2]"r"(&v2)
);
for (float i: result) cout << " " << i;
cout << "\n";
}
但结果是这样的:电话:+3.33452e +38 9. 18341 e-41
我真的是新来的组装。我的代码中的问题在哪里以及如何修复它们?谢谢你。
1条答案
按热度按时间lymgl2op1#
让我在前面加上一个大大的警告:正确掌握GCC内联asm很难,尤其是对于初学者。默认建议为don't use it。https://stackoverflow.com/tags/inline-assembly/info上有一些更通用的资源,但如果可能的话,我会从编写独立的汇编函数(在它们自己的.s文件中)开始。如果你从内联汇编开始,你基本上把自己放在了必须学习汇编语言的位置,同时学习高级(和缺乏文档的)编译器设计。
也就是说,您的代码对于初学者来说非常好,因为它的六行代码中只有三个bug。这些bug如下:
result
操作数实际上是一个 input,而不是一个output。虽然你要在数组中存储数据,但asm块的操作数是数组的 * 地址 *。换句话说,无论哪个寄存器被分配给该操作数(当我构建它时,它碰巧是x 0),您都需要编译器在执行asm块之前用result
* 的地址填充它。如果它是一个输出,你告诉编译器你不关心块之前的寄存器中有什么,但是它的值应该在块之后存储到result
中。这意味着,str q31, [%[r]]
将数据存储到一个完全随机的地址;你很幸运它没有崩溃或损坏数据。(实际上,在这个例子中发生的是编译器认为
vec1
只需要作为输入,result
只需要作为输出,默认情况下,它假设输入在产生输出之前被消耗,所以它将它们 * 都 * 分配给寄存器x 0。因此,您的结果实际上被存储回vec1
,而result
的内容仍然是未初始化的垃圾,这就是打印出来的内容。)q30, q31
,你必须将它们声明为“clobbered”;否则编译器可能会在其中保存重要数据。在实践中,除非绝对必要,否则通常不会引用显式寄存器,但可以适当地声明操作数,以便编译器为您选择它们。同样,您通常不会在asm块中执行自己的加载和存储;你把你的操作数变成数据的内容而不是它们的地址,然后编译器为你加载和存储。不过,如果你才刚开始也没关系。
memory
clobber。这里看起来像vec1
和vec2
是显式操作数,但这些数组的 * 内容 * 不是操作数,只是它们的 * 地址 *。正如你所看到的,这变得非常微妙。有一些方法可以使用m
约束来处理这个问题,更多细节请参见How can I indicate that the memory pointed to by an inline ASM argument may be used?,但对于非Maven来说,使用memory
clobber更安全。所以一个固定的版本看起来像这样: