我必须使用内联ARM汇编在C++程序中实现向量加法。
我写了这段代码:
#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.18341e-41 -2.23081e+25 9.18341e-41
我真的是装配新手。 我的代码中的问题在哪里以及如何解决它们?谢谢。
让我在前面加上一个重要的警告:正确使用 GCC 内联汇编很难,尤其是对于初学者而言。默认建议是不要使用它。 https://stackoverflow.com/tags/inline-assembly/info 上有一些更通用的资源,但如果可能的话,我会先编写独立的汇编函数(在它们自己的 .s 文件中)。如果你从内联汇编开始,你基本上将自己置于必须学习汇编语言的位置 simultaneously 与高级(和文档很少)编译器设计。
也就是说,您的代码对于初学者来说非常好,因为它的六行代码中只有三个错误。错误如下:
result
操作数实际上是一个input,而不是输出。虽然您要将数据存储在数组中,但 asm 块的操作数是数组的address。换句话说,无论分配给该操作数的寄存器是什么(我构建它时恰好是 x0),您都需要编译器在执行 asm 块之前用 result
的地址填充它。如果它是一个输出,你告诉编译器你不关心块之前那个寄存器中的内容,但它的值应该在之后存储到 result
中。所以这意味着,就目前而言,
str q31, [%[r]]
正在将数据存储到一个完全随机的地址;你很幸运它没有崩溃或损坏数据。(实际上,在这种情况下发生的事情是编译器认为
vec1
只需要作为输入,而
result
只需要作为输出,默认情况下它假设输入在输出产生之前被消耗,所以它分配给他们both注册x0。因此你的结果实际上被存储回
vec1
,并且
result
的内容仍然是未初始化的垃圾,这就是打印出来的内容。)
q30, q31
,你必须将它们声明为“clobbered”;否则编译器可能会在其中保留重要数据。实际上,除非绝对必要,否则通常不会引用显式寄存器,但会适当地声明操作数,以便编译器为您选择它们。同样,您通常不会在 asm 块中进行自己的加载和存储;你让你的操作数成为数据的
contents而不是它们的地址,然后编译器为你加载和存储。但如果你刚刚开始也没关系。
memory
clobber。这里看起来
vec1
和
vec2
是明确的操作数,但这些数组的contents 不是操作数,只是它们的 addresses。如您所见,这变得非常微妙。有一些方法可以使用
m
约束来处理这个问题,请参阅How can I indicate that the memory *pointed* to by an inline ASM argument may be used?了解更多详细信息,但对于非专家来说更安全使用
memory
clobber.
asm("ldr q31, [%[vec1]]\n"
"ldr q30, [%[vec2]]\n"
"FADD v31.4S, v31.4S, v30.4S\n"
"str q31, [%[r]]\n"
: // no outputs
: [r]"r"(result), [vec1]"r"(&v1), [vec2]"r"(&v2)
: "q30", "q31", "memory");