在 ARM 中将向量寄存器作为 float32x4_t C 变量进行操作

Manipulate vector register as float32x4_t C variable in ARM

我在 ARM 中使用内联汇编进行科学应用。 在我的汇编代码中,我必须(见最后的注释)名义上指出我想使用哪个向量寄存器。例如,在我的代码中,我有 asm volatile("fadd v12.4S, v12.4S, v7.4S") 在向量寄存器 7 和 12 之间执行 向量浮点加法 ,将结果存储在向量寄存器 12 中,以及其他内联组装说明。

在 'critical' 汇编代码部分之后,我想检索所述结果变量并将它们作为 C 中的 arm neon 变量对其进行操作。在我的例子中,向量将具有 4x 32 位变量,因此它们将是 float32x4_t.

类型

到目前为止我可以做类似的事情:

float32_t my_var[4];
asm volatile("st1  {v12.4S}, [%[addr]]\n\t" : : [addr]"r"(my_var) :  "x0",  "x1");
/*from here on I can operate on my_var[0], my_var[1], etc without having to write asm code*/

也就是说,我正在使用向量存储指令将向量寄存器的内容写入 C 向量变量。这将导致对该变量的后续访问被加载,我想避免这种情况,因为该变量已经存在于寄存器中。

我想要类似

的东西
float32x4_t my_var;
asm volatile("some code that make sure my_var 'binds' to vector 12");
/*from here on I could use intrinsic such as vgetq_lane_f32(my_var, 1) to get each value of the vector and not having to write asm code also*/

但是,我找不到执行第二种方法的方法。 This old question 有类似的担忧,但它是针对较旧的 ARM ISA(我的目标是 v8),并从(而不是存储到)单个(不是向量)变量加载。

注意:我不能从一开始就使用内部调用(这会使事情变得更容易),因为我正在模拟器中对新指令进行建模,并且我需要为该部分编写低级汇编。

您可以使用 w machine constraint 将 SIMD 寄存器作为操作数传递给内联汇编语句。这会导致编译器为您选择一个 SIMD 寄存器。

float32x4_t add(float32x4_t a, float32x4_t b)
{
    float32x4_t c;

    asm ("fadd %0.4s, %1.4s, %2.4s" : "=w"(c) : "w"(a), "w"(b));

    return c;
}

请注意,允许编译器在内联汇编语句之间覆盖任意寄存器。无法阻止这种情况。

但是,您可以使用 local register variables 告诉编译器将哪个 SIMD 寄存器用于操作数。这并不能保证变量将始终驻留在指定的寄存器中,但至少可以保证在每个内联 asm 语句之前或之后将变量列为输入 resp。输出操作数(详见手册)。

float32x4_t add(float32x4_t a_, float32x4_t b_)
{
    register float32x4_t c asm ("v12");
    register float32x4_t a asm ("v12") = a_;
    register float32x4_t b asm ("v4") = b_;

    asm ("fadd %0.4s, %1.4s, %2.4s" : "=w"(c) : "w"(a), "w"(b));

    return c;
}

理论上也应该可以在汇编时使用算术来构建正确的操作码,但似乎没有办法让 gcc 打印它选择的寄存器号而不进行任何修饰(嘘!)。假设有这样一个模板修饰符X,这样的代码可能是这样的:

float32x4_t add3(float32x4_t a, float32x4_t b)
{
    float32x4_t c;

    asm (".inst 0x4e20d40 + %X0 + (%X1<<5) + (%X2<<16)" : "=w"(c) : "w"(a), "w"(b));

    return c;
}

如果您需要此功能,可能值得将对此类内容的支持修补到您的本地 gcc/clang 构建中。