首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >GCC矢量扩展与ARM霓虹灯的内存对齐问题

GCC矢量扩展与ARM霓虹灯的内存对齐问题
EN

Stack Overflow用户
提问于 2020-10-09 21:33:50
回答 1查看 802关注 0票数 0

问题描述

我正在尝试用GCC向量扩展编写优化代码。因此,我定义了一个联合结构,如

代码语言:javascript
复制
#include <arm_neon.h>

typedef int32_t    v4si __attribute__ ((vector_size (16)));
typedef float32_t  v4sf __attribute__ ((vector_size (16)));

union v128
{
    int32x4_t   m128i;
    float32x4_t m128f;
    v4si        si;
    v4sf        sf;
};

v128 x,y;

编写像x.sf *= y.sf这样的代码通常会由于总线错误而导致崩溃。使用gdb进行的检查总是显示,在所有这些崩溃情况下,至少有一个变量只对8字节,而不是16字节。然而,当我使用优化选项"-O2“编译时,这些崩溃的情况发生得更少。

有没有gcc/g++编译器的选项,它总是保证GCC向量的16位对齐?既然"-O2“支持整束优化,那么有谁知道哪种特定的优化会导致总线错误的频率低得多呢?

我正在raspberry pi 3上编译和测试我的代码。

代码语言:javascript
复制
-march=armv8-a+crc -mtune=cortex-a53 -mfloat-abi=hard -mfpu=neon-fp-armv8 -funsafe-math-optimizations

最小代码示例

simd_numeric_test.cpp:

代码语言:javascript
复制
#include <random>
#include <limits>
#include <cfloat>
#include <type_traits>
#include <cassert>
#include <arm_neon.h>


typedef int32_t    v4si __attribute__ ((vector_size (16), aligned(16)));
typedef float32_t  v4sf __attribute__ ((vector_size (16), aligned(16)));


typedef int32x4_t   m128i_t; // __attribute__ ((aligned(16)));
typedef float32x4_t m128f_t; // __attribute__ ((aligned(16)));

union v128
{
    m128i_t m128i;
    m128f_t m128f;
    v4si    si;
    v4sf    sf;
};
static_assert( sizeof(v128) == 16 );


struct vf32_t
{
    v128 val;

    static constexpr size_t num_items() { return (sizeof(val) / sizeof(float32_t)); }

    inline
    const vf32_t& operator+=( const vf32_t& other ) { val.sf += other.val.sf; return *this; }

    inline
    const float32_t* cbegin() const { return &(val.sf[0]); }

    inline
    const float32_t* cend() const { return &(val.sf[num_items()]); }
};
static_assert( sizeof(vf32_t) == 16 );


class CSimdNumericTest
{
protected:

    const size_t m_numElemInSimd     = vf32_t::num_items();
    
    const int m_randomSeed_u         = 69;
    const int m_repeats_u            = 10000;

    const float32_t m_maxFloatVal_f32;// = 43.f;

    std::default_random_engine                m_rand;
    std::uniform_real_distribution<float32_t> m_floatSampler;

    void test_binary_assign_vv_operation( const vf32_t a_v32, const vf32_t b_v32 ) const;

public:

    void float32_base_op_test();

    CSimdNumericTest()
        : m_maxFloatVal_f32( std::ceil( std::pow( std::numeric_limits<float32_t>::max(),
                                                  1.f / static_cast<float32_t>( m_numElemInSimd  ) ) ) )
        , m_rand( m_randomSeed_u )
        , m_floatSampler( -m_maxFloatVal_f32, m_maxFloatVal_f32 )
    {}
};

void CSimdNumericTest::test_binary_assign_vv_operation( const vf32_t a_v32, const vf32_t b_v32 ) const
{
    vf32_t x = a_v32;

    x += b_v32;

    auto aIter = a_v32.cbegin();
    auto bIter = b_v32.cbegin();
    for ( auto xIter = x.cbegin(); xIter != x.cend();
           ++xIter, ++aIter, ++bIter ) {
        float32_t rx = *aIter;
        rx += *bIter;
        assert( rx == *xIter );
    }
}

void CSimdNumericTest::float32_base_op_test()
{
    vf32_t a_v32, b_v32;

    const float32_t l_minFloat_f32 = 1. / m_maxFloatVal_f32;

    for ( int n = 0; n < m_repeats_u; ++n )
    {
        for ( size_t i = 0; i < vf32_t::num_items(); ++i )
        {
            a_v32.val.sf[i] = m_floatSampler( m_rand );
            b_v32.val.sf[i] = m_floatSampler( m_rand );
        }
        test_binary_assign_vv_operation( a_v32, b_v32 );
    }
}

int main(int argc, char **argv) {
  
    CSimdNumericTest test;
    test.float32_base_op_test();
    return 0;
}

我把所有东西都编好了

代码语言:javascript
复制
arm-linux-gnueabihf-g++ -c -o simd_numeric_test_neon.o simd_numeric_test.cpp -pipe -fsigned-char -pthread -ftree-vectorize -Wall -Wextra -Wdate-time -Wformat -Werror=format-security -ggdb3 -O0 -march=armv8-a+crc -mtune=cortex-a53 -mfloat-abi=hard -mfpu=neon-fp-armv8 -funsafe-math-optimizations -Wno-psabi 
arm-linux-gnueabihf-g++ -pthread -lpthread -lstdc++ -o simd_test_neon simd_numeric_test_neon.o

汇编的结果:

崩溃出现在赋值语句中:

代码语言:javascript
复制
x += b_v32;

螺栓连接

进一步调查结果

现在我注意到,所有的崩溃都是在使用按值传递函数参数时发生的。当原始向量变量仍然正确对齐时,复制的函数参数不再对齐。因此,当我将pass-by-value替换为pass-by-reference时,可执行文件工作正常。

代码语言:javascript
复制
void test_binary_assign_vv_operation( const vf32_t a_v32, const vf32_t b_v32 )

代码语言:javascript
复制
void test_binary_assign_vv_operation( const vf32_t& a_v32, const vf32_t& b_v32 )

我在所有的公共汽车事故中都观察到了这种模式。

然而,这种观察并没有带来真正的解决办法。有许多使用pass-by-value.的函数(例如,在C++STL中)

是否有任何g++参数hat也为矢量化函数参数提供了正确的内存对齐方式?这可能是g++的错误吗?

事先非常感谢

EN

回答 1

Stack Overflow用户

发布于 2020-10-10 19:30:09

我同意你的观点,这是gcc在ARM / AArch64和其他几个目标(但不是x86)上的错误。

当您有一个需要额外对齐的类型,但它可以在寄存器中传递时,问题似乎就出现了。如果将这样的对象作为函数参数传递,并且被调用的函数接受其地址,则对象会溢出到堆栈中,但没有必要的对齐。然后,可以通过引用另一个函数传递未对齐的对象,从而导致崩溃。

它可以在C中复制并且不带矢量。下面是一个测试用例;用-O0编译以避免内联。(但即使在打开优化时,函数本身仍然编译错误。)

代码语言:javascript
复制
#include <stdio.h>

typedef int V __attribute__((aligned(64)));

void f3(V *p) {
  printf("%p\n", (void *)p);
}

void f2(V x) {
    //volatile int blah = 17;
    f3(&x);
}

int main(void) {
  f2(-43);
  return 0;
}

gcc在arm-linux-gnueabihfaarch64-linux-gnu上运行到10.2,它打印的地址不是64字节对齐。(您可能不得不取消对volatile int声明的注释,以防堆栈由于巧合而正确对齐。)

检查生成的程序集显示gcc将x泄漏到堆栈中,并且没有尝试对齐它。我相信,ABI堆栈对齐对于ARM只有8个字节,对于AArch64只有16个字节,因此需要手动对齐。

在手臂上:

代码语言:javascript
复制
f2:
        push    {r7, lr}
        sub     sp, sp, #8
        add     r7, sp, #0
        str     r0, [r7]
        mov     r3, r7
        mov     r0, r3
        bl      f3(PLT)
        nop
        adds    r7, r7, #8
        mov     sp, r7
        pop     {r7, pc}

在AArch64上:

代码语言:javascript
复制
f2:
        stp     x29, x30, [sp, -32]!
        mov     x29, sp
        str     w0, [sp, 16]
        add     x0, sp, 16
        bl      f3
        nop
        ldp     x29, x30, [sp], 32
        ret

您可以在自己的函数中通过将函数参数分配给一个临时变量并传递它来解决错误,但当然,正如您所说的,这无助于从标准库模板生成的函数。

它看起来像clang正确地处理对齐,所以这可能是您的另一个选择。

更新: bug出现在gcc 20201010的主干中,我还可以在alpha、sparc64和mips目标上复制它(在仿真中)。然而,x86-64生成正确的对齐代码。我把这件事报告给gcc虫97473了。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64287587

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档