Gcc 带错误的ARM内联汇编代码;asm中的不可能约束”;

Gcc 带错误的ARM内联汇编代码;asm中的不可能约束”;,gcc,assembly,arm,inline,neon,Gcc,Assembly,Arm,Inline,Neon,我正在尝试优化以下代码complex.cpp: typedef struct { float re; float im; } dcmplx; dcmplx ComplexConv(int len, dcmplx *hat, dcmplx *buf) { int i; dcmplx z, xout; xout.re = xout.im = 0.0; asm volatile ( "movs r3, #0\n\t" ".

我正在尝试优化以下代码complex.cpp:

typedef struct {
    float re;
    float im;
} dcmplx;

dcmplx ComplexConv(int len, dcmplx *hat, dcmplx *buf)
{
    int    i;
    dcmplx    z, xout;

    xout.re = xout.im = 0.0;
    asm volatile (
    "movs r3, #0\n\t"
    ".loop:\n\t"
    "vldr s11, [%[hat], #4]\n\t"
    "vldr s13, [%[hat]]\n\t"
    "vneg.f32 s11, s11\n\t"
    "vldr s15, [%[buf], #4]\n\t"
    "vldr s12, [%[buf]]\n\t"
    "vmul.f32 s14, s15, s13\n\t"
    "vmul.f32 s15, s11, s15\n\t"
    "adds %[hat], #8\n\t"
    "vmla.f32 s14, s11, s12\n\t"
    "vnmls.f32 s15, s12, s13\n\t"
    "adds %[buf], #8\n\t"
    "vadd.f32 s1, s1, s14\n\t"
    "vadd.f32 s0, s0, s15\n\t"
    "adds r3, r3, #1\n\t"
    "cmp r3, r0\n\t"
    "bne .loop\n\t"
    : "=r"(xout)
    : [hat]"r"(hat),[buf]"r"(buf) 
    : "s0","cc"
    );
    return xout;
}
当使用“arm-linux-gnueabihf-g++-c complex.cpp-o complex.o-mfpu=neon”编译时, 我得到了以下错误:“asm”中的不可能约束

当我注释掉“=r”(xout)时,编译不会抱怨,但如何将寄存器's0'的结果输入xout

此外,如果r0包含返回值,但返回类型是一个复杂的结构,因为r0只有32位,那么它是如何工作的?登记

我在这里发布的原始c代码:

dcmplx ComplexConv(int len, dcmplx *hat, dcmplx *buf)
{
    int    i;
    dcmplx    z, xout;
    xout.re = xout.im = 0.0;
    for(int i = 0; i < len; i++) {
        z = BI_dcmul(BI_dconjg(hat[i]),buf[i]);
        xout = BI_dcadd(xout,z);
    }
    return xout;
}
dcmplx BI_dcmul(dcmplx x, dcmplx y)
{
    dcmplx    z;
    z.re = x.re * y.re - x.im * y.im;
    z.im = x.im * y.re + x.re * y.im;
    return z;
}
dcmplx BI_dconjg(dcmplx x)
{
    dcmplx    y;
    y.re = x.re;
    y.im = -x.im;
    return y;
}
dcmplx BI_dcadd(dcmplx x, dcmplx y)
{
    dcmplx    z;
    z.re = x.re + y.re;
    z.im = x.im + y.im;
    return z;
}
dcmplx ComplexConv(int-len,dcmplx*hat,dcmplx*buf)
{
int i;
dcmplx z,xout;
xout.re=xout.im=0.0;
对于(int i=0;i
您的内联汇编代码会犯许多错误:

  • 它尝试使用64位结构作为具有32位输出寄存器(
    “=r”
    )约束的操作数。这就是错误的原因
  • 它不在任何地方使用该输出操作数
  • 它不会告诉编译器输出的实际位置(S0/S1)
  • 它不会告诉编译器
    len
    应该是一个输入
  • 它在不通知编译器的情况下对多个寄存器R3、S11、S12、S13、S14、S14进行缓冲
  • 它使用了一个标签
    .loop
    ,不必要地阻止编译器将代码内联到多个位置
  • 它看起来并不等同于你所显示的C++代码,而是计算其他的东西。
我不想费心解释如何修复所有这些错误,因为你。你可以用C++编写代码,让编译器做矢量化。

例如编译下面的代码,相当于您的示例C++代码,GCC 4.9和<代码> -O3-FUNSAT数学优化< /Cord>选项:

dcmplx ComplexConv(int len, dcmplx *hat, dcmplx *buf)
{
    int    i;
    dcmplx xout;
    xout.re = xout.im = 0.0;
    for (i = 0; i < len; i++) {
        xout.re += hat[i].re * buf[i].re + hat[i].im * buf[i].im;
        xout.im += hat[i].re * buf[i].im - hat[i].im * buf[i].re;
    }
    return xout;
}
基于您的内联汇编代码,编译器生成的结果可能比您自己尝试对其进行矢量化时得到的结果要好

-funsafe数学优化
是必要的,因为NEON指令不完全符合IEEE 754。作为缔约国:

如果选定的浮点硬件包含NEON扩展名 (例如,
-mfpu='neon'
),请注意浮点运算不是 由GCC的自动矢量化过程生成,除非
-还指定了funsafe数学优化。这是因为NEON硬件没有完全实现IEEE 754标准
浮点运算(特别是非规范值)
为零),因此使用霓虹灯指令可能会导致
精确性

我还应该注意,如果不滚动自己的复杂类型,编译器生成的代码几乎与上面的代码一样好,如以下示例所示:

#include <complex>
typedef std::complex<float> complex;
complex ComplexConv_std(int len, complex *hat, complex *buf)
{
    int    i;
    complex xout(0.0f, 0.0f); 
    for (i = 0; i < len; i++) {
        xout += std::conj(hat[i]) * buf[i];
    }
    return xout;
}
通过说它需要8字节(64位)对齐,这允许编译器跳过检查以查看它是否正确对齐,然后转而使用较慢的标量实现

现在,假设您对GCC如何将代码矢量化感到不满意,并认为您可以做得更好。这是否证明使用内联汇编是合理的?不,下一步要尝试的是。使用内嵌就像普通的C++编程一样,你不必担心你需要遵循的一系列特殊规则。例如,下面是我如何将上面的矢量化程序集转换为使用内部函数的未经测试的代码:

#include <assert.h>
#include <arm_neon.h>
dcmplx ComplexConv(int len, dcmplx *hat, dcmplx *buf)
{
    int    i;
    dcmplx xout;

    /* everything needs to be suitably aligned */
    assert(len % 4 == 0);
    assert(((unsigned) hat % 8) == 0);
    assert(((unsigned) buf % 8) == 0);

    float32x4_t re, im;
    for (i = 0; i < len; i += 4) {
        float32x4x2_t h = vld2q_f32(&hat[i].re);
        float32x4x2_t b = vld2q_f32(&buf[i].re);
        re = vaddq_f32(re, vmlaq_f32(vmulq_f32(h.val[0], b.val[0]),
                                     b.val[1], h.val[1]));
        im = vaddq_f32(im, vmlsq_f32(vmulq_f32(h.val[1], b.val[1]),
                                     b.val[0], h.val[0]));
    }
    float32x2_t re_tmp = vadd_f32(vget_low_f32(re), vget_high_f32(re));
    float32x2_t im_tmp = vadd_f32(vget_low_f32(im), vget_high_f32(im));
    xout.re = vget_lane_f32(vpadd_f32(re_tmp, re_tmp), 0);
    xout.im = vget_lane_f32(vpadd_f32(im_tmp, im_tmp), 0);
    return xout;
}
#包括
#包括
dcmplx ComplexConv(int len,dcmplx*hat,dcmplx*buf)
{
int i;
dcmplx-xout;
/*一切都需要适当地调整*/
断言(len%4==0);
断言(((未签名)帽子%8)==0);
断言(((无符号)buf%8)==0);
浮动32x4_t re,im;
对于(i=0;i

最后,如果这还不够好,您需要尽可能调整每一点性能,那么使用内联汇编仍然不是一个好主意。相反,您的最后手段应该是使用常规装配。由于大部分函数都是在汇编中重写的,所以最好完全在汇编中编写。这意味着您不必担心告诉编译器您在内联程序集中所做的一切。您只需要遵守ARM ABI,这可能非常棘手,但比使用内联汇编来纠正所有问题要容易得多。

这里没有理由使用内联汇编。只使用普通C++。还应该考虑使用<代码> STD::复合< /代码>,而不是自己的复杂类型。也就是说,在这段代码中没有为%0赋值,因此xout的内容将是未定义的。对于输出结构,您是否考虑过传递
dcmplx*
?@RossRidge,这里使用内联汇编的原因是arm gcc只使用vfp rath生成代码
#include <assert.h>
#include <arm_neon.h>
dcmplx ComplexConv(int len, dcmplx *hat, dcmplx *buf)
{
    int    i;
    dcmplx xout;

    /* everything needs to be suitably aligned */
    assert(len % 4 == 0);
    assert(((unsigned) hat % 8) == 0);
    assert(((unsigned) buf % 8) == 0);

    float32x4_t re, im;
    for (i = 0; i < len; i += 4) {
        float32x4x2_t h = vld2q_f32(&hat[i].re);
        float32x4x2_t b = vld2q_f32(&buf[i].re);
        re = vaddq_f32(re, vmlaq_f32(vmulq_f32(h.val[0], b.val[0]),
                                     b.val[1], h.val[1]));
        im = vaddq_f32(im, vmlsq_f32(vmulq_f32(h.val[1], b.val[1]),
                                     b.val[0], h.val[0]));
    }
    float32x2_t re_tmp = vadd_f32(vget_low_f32(re), vget_high_f32(re));
    float32x2_t im_tmp = vadd_f32(vget_low_f32(im), vget_high_f32(im));
    xout.re = vget_lane_f32(vpadd_f32(re_tmp, re_tmp), 0);
    xout.im = vget_lane_f32(vpadd_f32(im_tmp, im_tmp), 0);
    return xout;
}