gcc - 错误为 "impossible constraint in asm"的 ARM 内联汇编代码

标签 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"
    ".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;
}

最佳答案

你的内联汇编代码有很多错误:

  • 它尝试使用 64 位结构作为具有 32 位输出寄存器 ("=r") 约束的操作数。这就是给您错误的原因。
  • 它不会在任何地方使用该输出操作数
  • 它没有告诉编译器输出实际在哪里(S0/S1)
  • 它没有告诉编译器 len 应该是一个输入
  • 它破坏了一些寄存器,R3、S11、S12、S13、S14、S14,而不告诉编译器。
  • 它使用标签 .loop 不必要地阻止编译器在多个地方内联您的代码。
  • 它实际上并不等同于您展示的 C++ 代码,而是计算其他内容。

我不会费心去解释如何修复所有这些错误,因为你 shouldn't be using inline assembly .您可以用 C++ 编写代码,让编译器进行矢量化。

例如,使用 GCC 4.9 和 -O3 -funsafe-math-optimizations 选项编译以下代码,相当于您的示例 C++ 代码:

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

生成以下程序集作为其内部循环:

.L97:
    add lr, lr, #1
    cmp ip, lr
    vld2.32 {d20-d23}, [r5]!
    vld2.32 {d24-d27}, [r4]!
    vmul.f32    q15, q12, q10
    vmul.f32    q14, q13, q10
    vmla.f32    q15, q13, q11
    vmls.f32    q14, q12, q11
    vadd.f32    q9, q9, q15
    vadd.f32    q8, q8, q14
    bhi .L97

根据您的内联汇编代码,编译器生成的结果可能比您尝试自己对其进行矢量化时产生的结果要好。

-funsafe-math-optimizations 是必需的,因为 NEON 指令不完全符合 IEEE 754。作为GCC documentation状态:

If the selected floating-point hardware includes the NEON extension (e.g. -mfpu=‘neon’), note that floating-point operations are not generated by GCC's auto-vectorization pass unless -funsafe-math-optimizations is also specified. This is because NEON hardware does not fully implement the IEEE 754 standard for floating-point arithmetic (in particular denormal values are treated as zero), so the use of NEON instructions may lead to a loss of precision.

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

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

然而,使用您自己的类型的一个优点是,您可以改进编译器生成的代码,只需对您声明 struct dcmplx 的方式做一个小改动:

typedef struct {
    float re;
    float im;
} __attribute__((aligned(8)) dcmplx;

通过说明它需要 8 字节(64 位)对齐,这允许编译器跳过检查以查看它是否适当对齐,然后转而使用较慢的标量实现。

现在,假设您对 GCC 向量化您的代码的方式不满意,并认为您可以做得更好。这会证明使用内联汇编是合理的吗?不,接下来要尝试的是 ARM NEON intrinsics .使用内部函数就像普通的 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;
}

最后,如果这还不够好并且您需要尽可能地调整每一点性能,那么使用内联汇编仍然不是一个好主意。相反,您最后的选择应该是使用常规程序集。由于您在汇编中重写了大部分功能,因此您还不如将其完全用汇编编写。这意味着您不必担心告诉编译器您在内联汇编中所做的一切。您只需要符合 ARM ABI,这可能已经够棘手了,但比使用内联汇编使所有内容都正确要容易得多。

关于gcc - 错误为 "impossible constraint in asm"的 ARM 内联汇编代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39635964/

相关文章:

c - gcc 编译时类型解析

iOS ARM64 系统调用

c++ - 如何在 Ubuntu 中使用 arm-linux-gnueabihf-g++ 在 amd64 系统上交叉编译 32 位 arm 架构的应用程序

c - 为什么用 void 关键字调用函数没有效果?

c - C中的结构问题

c++ - 在 Mac 10.9 Mavericks 上,clang 配置是否与 gcc 链接配置不同?

assembly - 为 x86 程序集绘制堆栈框架

assembly - 使用 EAX、EBX、ECX 等时出现 undefined symbol 异常

iphone - iOS:如何确定 CPU 类型,例如A4还是A5,或者指令集架构arm6还是arm7?

gcc - 如何让链接器和加载器在特定路径选择共享库?