带有错误“asm 中的不可能约束"的 ARM 内联汇编代码 [英] ARM inline assembly code with error "impossible constraint in asm"

查看:30
本文介绍了带有错误“asm 中的不可能约束"的 ARM 内联汇编代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to optimize the following code 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' 中的不可能约束.

When it is compiled with "arm-linux-gnueabihf-g++ -c complex.cpp -o complex.o -mfpu=neon", I got the following error: impossible constraint in 'asm'.

当我注释掉 "=r"(xout) 时,编译不会报错,但是如何将寄存器 's0' 的结果放入 xout 中?

When I comment out "=r"(xout), the compile doesn't complain, but how can I get result of register 's0' into xout?

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

Besides, how it works if r0 contains return value but the return type is a complicate structure, since r0 is only a 32-bit? register.

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

The original c code I post here:

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

推荐答案

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

Your inline assembly code makes a number of mistakes:

  • 它尝试使用 64 位结构作为具有 32 位输出寄存器 ("=r") 约束的操作数.这就是给你错误的原因.
  • 它不会在任何地方使用该输出操作数
  • 它不会告诉编译器输出的实际位置 (S0/S1)
  • 它不会告诉编译器 len 应该是一个输入
  • 它在不告诉编译器的情况下破坏了许多寄存器,R3、S11、S12、S13、S14、S14.
  • 它使用标签 .loop 不必要地阻止编译器在多个位置内联您的代码.
  • 它实际上并不等同于您展示的 C++ 代码,而是计算其他内容.
  • It tries to use a 64-bit structure as an operand with a 32-bit output register ("=r") constraint. This is what gives you the error.
  • It doesn't use that output operand anywhere
  • It doesn't tell the compiler where the output actually is (S0/S1)
  • It doesn't tell the compiler that len is supposed to be an input
  • It clobbers a number of registers, R3, S11, S12, S13, S14, S14, without telling the compiler.
  • It uses a label .loop that unnecessarily prevents the compiler from inlining your code in multiple places.
  • It doesn't actually appear to be the equivalent of the C++ code you've shown, calculating something else instead.

我不打算解释如何解决所有这些错误,因为您不应该使用内联汇编.您可以用 C++ 编写代码,然后让编译器进行矢量化.

I'm not going to bother to explain how you can fix all these mistakes, because you shouldn't be using inline assembly. You can write your code in C++ and let the compiler do the vectorization.

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

For example compiling following code, equivalent to your example C++ code, with GCC 4.9 and the -O3 -funsafe-math-optimizations options:

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

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

generates the following assembly as its inner loop:

.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

根据您的内联汇编代码,编译器生成的结果可能比您尝试自己矢量化时想出的要好.

Based on your inline assembly code, it's likely that the compiler generated better than what you would've come up with if you tried to vectorize it yourself.

-funsafe-math-optimizations 是必要的,因为 NEON 指令并不完全符合 IEEE 754.正如 GCC 文档 所述:

The -funsafe-math-optimizations is necessary because the NEON instructions aren't fully IEEE 754 conformant. As the GCC documentation states:

如果选定的浮点硬件包括 NEON 扩展(例如 -mfpu='neon'),注意浮点运算不是由 GCC 的自动矢量化过程生成,除非-funsafe-math-optimizations 也被指定.这是因为 NEON 硬件没有完全实现 IEEE 754 标准浮点运算(特别是非正规值被处理为零),因此使用 NEON 指令可能会导致丢失精度.

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.

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

I should also note that the compiler generates almost as good as code above if you don't roll your own complex type, like in the following example:

#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 的方式进行一个小的更改:

One advantage to using your own type however, is that you can improve the code compiler generates making one small change to how you declare struct dcmplx:

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

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

By saying it needs to be 8-byte (64-bit) aligned, this allows the compiler to skip the check to see if it is suitably aligned and then fall back on the slower scalar implementation instead.

现在,假设您对 GCC 对代码进行矢量化的方式不满意,并认为您可以做得更好.这会证明使用内联汇编是合理的吗?不,接下来要尝试的是 ARM NEON 内在函数.使用内部函数就像普通的 C++ 编程一样,您不必担心需要遵循的一堆特殊规则.例如,这里是我如何将上面的矢量化程序集转换为使用内在函数的未经测试的代码:

Now, hypothetically, lets say you were unsatisfied with how GCC vectorized your code and thought you could do better. Would this justify using inline assembly? No, the next thing to try are the ARM NEON intrinsics. Using intrinics is just like normal C++ programming, you don't have worry about a bunch of special rules you need to follow. For example here's how I converted the vectorized assembly above into this untested code that uses intrinsics:

#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,这可能已经够棘手了,但比使用内联汇编使一切正确要容易得多.

Finally if this wasn't good enough and you needed to tweak out every bit of performance you could then it's still not a good idea to use inline assembly. Instead your last resort should be to use regular assembly instead. Since your rewriting most of the function in assembly, you might as well write it completely in assembly. That means you don't have worry about telling the compiler about everything you're doing in the inline assembly. You only need to conform to the ARM ABI, which can be tricky enough, but is a lot easier than getting everything correct with inline assembly.

这篇关于带有错误“asm 中的不可能约束"的 ARM 内联汇编代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆