使用 __builtin_msa_ld_* 后如何转换为无符号向量类型

Posted

技术标签:

【中文标题】使用 __builtin_msa_ld_* 后如何转换为无符号向量类型【英文标题】:How to cast to unsigned vector type after using __builtin_msa_ld_* 【发布时间】:2018-10-21 05:58:48 【问题描述】:

我正在使用Codescape GCC Toolchain 评估MIPS SIMD Architecture (MSA) 编程。关于 MSA 和内置函数的信息并不多。 (据我所知,只有两个 MSA cpu,P5600 和 Warrior I6400,它们在几年前首次面世。

我的测试程序如下。

#include <msa.h>
#include <stdint.h>

#define ALIGN16 __attribute__((aligned(16)))

int main(int argc, char* argv[])

    ALIGN16 uint32_t a[] = 64, 128, 256, 512;
    ALIGN16 uint32_t b[] = 1024, 2048, 4096, 8192;
    ALIGN16 uint32_t c[4];

    v4u32 va = __builtin_msa_ld_w (a, 0);
    v4u32 vb = __builtin_msa_ld_w (b, 0);

    v4u32 vc = __builtin_msa_adds_u_w (va, vb);
    __builtin_msa_st_w (vc, c, 0);

    return 0;

编译程序会导致如下所示的错误。问题是,vector loads return a signed vector 但我的向量是无符号的。矢量商店也有类似的问题。

// The 4 vector loads provided through builtins
v16i8 __builtin_msa_ld_b (void *, imm_n512_511);    // byte
v8i16 __builtin_msa_ld_h (void *, imm_n1024_1022);  // half word
v4i32 __builtin_msa_ld_w (void *, imm_n2048_2044);  // word
v2i64 __builtin_msa_ld_d (void *, imm_n4096_4088);  // double word

imm_n512_511 和朋友们在 GCC 手册中讨论,6.59.16 MIPS SIMD Architecture (MSA) Support)。

我在MIPS SIMD Architecture 阅读了 MIPS 论文(?),但它没有讨论如何在整数向量类型之间进行转换。有很多浮点转换指令,但没有整数类型。

简单的强制转换是在整数向量类型之间进行转换的首选方式吗?或者还有什么我应该做的吗?


MSA$ mips-img-linux-gnu-gcc.exe -mmsa test.c -c
test.c: In function 'main':
test.c:12:2: note: use -flax-vector-conversions to permit conversions between ve
ctors with differing element types or numbers of subparts
  v4u32 va = __builtin_msa_ld_w (a, 0);
  ^~~~~
test.c:12:13: error: incompatible types when initializing type 'v4u32 aka __vec
tor(4) unsigned int' using type '__vector(4) int'
  v4u32 va = __builtin_msa_ld_w (a, 0);
             ^~~~~~~~~~~~~~~~~~
test.c:13:13: error: incompatible types when initializing type 'v4u32 aka __vec
tor(4) unsigned int' using type '__vector(4) int'
  v4u32 vb = __builtin_msa_ld_w (b, 0);
             ^~~~~~~~~~~~~~~~~~
test.c:16:22: error: incompatible type for argument 1 of '__builtin_msa_st_w'
  __builtin_msa_st_w (vc, c, 0);
                      ^~
test.c:16:22: note: expected '__vector(4) int' but argument is of type 'v4u32 a
ka __vector(4) unsigned int'

【问题讨论】:

为什么不使用vavb 而不是ab?文档还说,“加载/存储指令不需要 128 位(16 字节)内存地址对齐。”,所以我认为您不需要 ALIGN16。我认为您不必担心“MSA 通过一组超过 150 条新指令来补充完善的 MIPS 架构,这些指令在 8、16、32 和 64 位整数的 32 个向量寄存器上运行,16 - 和 32 位定点,或 32 位和 64 位浮点数据元素....”,看起来像一个简单的转换将正确地完成这项工作(当然,如果真正的整数是无符号的)。跨度> 感谢 @Stargateur - ALIGN16 来自 MIPS SIMD Architecture,第 5.1 节矢量数据类型和内在函数,第 10 页:“建议将矢量数据与矢量的大小对齐注册”。我认为“向量寄存器的大小”的重要性在于,DSP有64位向量寄存器,而MSA有128位向量寄存器。 好吧,recommanded 与 required 不同,也许最好对齐它们,我没有阅读所有文档。另外我认为他们没有添加加载无符号的特定指令来保存一些指令,因为我认为他们的加载对两个符号都有效。也许,您应该添加自己的包装函数,该函数会在您加载和存储无符号整数时为您转换向量。 【参考方案1】:

要么使用强制类型转换和-flax-vector-conversions,要么使用联合类型来表示向量寄存器并显式处理该联合类型。 GCC 明确支持这种类型的双关语。

例如,您可以声明一个msa128 类型,

typedef union __attribute__ ((aligned (16))) 
    v2u64   u64;
    v2i64   i64;
    v2f64   f64;
    v4u32   u32;
    v4i32   i32;
    v4f32   f32;
    v8u16   u16;
    v8i16   i16;
    v16u8   u8;
    v16i8   i8;
 msa128;

然后让您的代码在 msa128 类型上显式工作。你的示例程序可以写成

    uint32_t a[4] =  64, 128, 256, 512 ;
    uint32_t b[4] =  1024, 2048, 4096, 8192 ;
    uint32_t c[4];
    msa128   va, vb, vc;

    va.i32 = __builtin_msa_ld_w(a, 0);
    vb.i32 = __builtin_msa_ld_w(b, 0);
    vc.u32 = __builtin_msa_adds_u_w(va.u32, vb.u32);
    __builtin_msa_st_w(vc.i32, c, 0);

显然,记住需要使用的确切类型变得非常烦人,因此一些静态内联帮助函数肯定会很方便:

static inline msa128  msa128_load64(const void *from, const int imm)
 return (msa128) .i64 = __builtin_msa_ld_d(from, imm);  

static inline msa128  msa128_load32(const void *from, const int imm)
 return (msa128) .i32 = __builtin_msa_ld_w(from, imm);  

static inline msa128  msa128_load16(const void *from, const int imm)
 return (msa128) .i16 = __builtin_msa_ld_h(from, imm);  

static inline msa128  msa128_load8(const void *from, const int imm)
 return (msa128) .i8  = __builtin_msa_ld_b(from, imm);  

static inline void  msa128_store64(const msa128 val, void *to, const int imm)
 __builtin_msa_st_d(val.i64, to, imm); 

static inline void  msa128_store32(const msa128 val, void *to, const int imm)
 __builtin_msa_st_w(val.i32, to, imm); 

static inline void  msa128_store16(const msa128 val, void *to, const int imm)
 __builtin_msa_st_h(val.i16, to, imm); 

static inline void  msa128_store8(const msa128 val, void *to, const int imm)
 __builtin_msa_st_b(val.i8, to, imm); 

例如,二元 AND、OR、NOR 和 XOR 操作是

static inline msa128  msa128_and(const msa128 a, const msa128 b)
 return (msa128) .u8 = __builtin_msa_and_v(a, b) ; 

static inline msa128  msa128_or(const msa128 a, const msa128 b)
 return (msa128) .u8 = __builtin_msa_or_v(a, b) ; 

static inline msa128  msa128_nor(const msa128 a, const msa128 b)
 return (msa128) .u8 = __builtin_msa_nor_v(a, b) ; 

static inline msa128  msa128_xor(const msa128 a, const msa128 b)
 return (msa128) .u8 = __builtin_msa_xor_v(a, b) ; 

创建一些宏来表示数组形式的向量可能不会有什么坏处:

#define  MSA128_U64(...)  ((msa128) .u64 =  __VA_ARGS__ )
#define  MSA128_I64(...)  ((msa128) .i64 =  __VA_ARGS__ )
#define  MSA128_F64(...)  ((msa128) .f64 =  __VA_ARGS__ )
#define  MSA128_U32(...)  ((msa128) .u32 =  __VA_ARGS__ )
#define  MSA128_I32(...)  ((msa128) .i32 =  __VA_ARGS__ )
#define  MSA128_F32(...)  ((msa128) .f32 =  __VA_ARGS__ )
#define  MSA128_U16(...)  ((msa128) .u16 =  __VA_ARGS__ )
#define  MSA128_I16(...)  ((msa128) .i16 =  __VA_ARGS__ )
#define  MSA128_U8(...)   ((msa128) .u8  =  __VA_ARGS__ )
#define  MSA128_I8(...)   ((msa128) .i8  =  __VA_ARGS__ )

我建议这种特定于 GCC 的方法的原因是内置函数是特定于 GCC 的。除了联合类型之外,它非常接近 GCC 在&lt;immintrin.h&gt; 中实现 Intel/AMD 向量内在函数的方式。

【讨论】:

谢谢@Nominal。最终我需要在 C++ 中使用它,所以我不能使用联合技巧。 (抱歉,我没有添加 C++ 标签,因为评论 C 和 C++ 的人是不同的语言......)。 @jww 您的问题是标记 C 和 C++ 错误的典型情况,如果您需要两种语言的答案,您必须创建两个问题,然后由 C++ 专家或 C 决定答案对于 C 和 C++ 都是正确的,并且会以重复的形式关闭。例如,我可以回答(并且我做了但在评论中)C 部分,但我不能确定 C++ 部分,因为我从未阅读过 C++ 标准。除非您的问题涉及兼容性问题(特定问题),否则不要标记两种语言。 谢谢@nominal。是的,联合技巧可以是 C++ 中的未定义行为如果您访问非活动成员(此处依赖于此)。另请参阅Accessing inactive union member and undefined behavior? 我发现我可以通过获取向量变量的地址并使用memcpy 以使 C 和 C++ 满意的方式压缩警告,但这是一个代码缺陷。太糟糕了,MIPS 没有像 ARM 内在函数那样的强制转换(比如 vreinterpretq_i32_u32)。 再次感谢@nominal。如果 MIPS 专家出现并提供更好的东西,我可能需要接受。 @jww:别担心!对我来说,只有答案的适用性/有用性很重要。 (我承认,我是对 C 和 C++ 差异很重要的人之一——但在我的辩护中,这是因为答案是(或应该是)完全不同的,以提高效率。这 is其中一种情况。)由于我将 C 用于我的低级代码,并且没有在 C++ 中使用向量内在函数,我不确定 C++ 中的最佳解决方案是什么,但我今天稍后会看看,如果我发现比 memcpy() 更好的方法,请编辑我的答案。【参考方案2】:

这是一个同时适用于 C 和 C++ 的替代方案。它对寄存器变量执行memcpy。内联函数借鉴了 ARM NEON 支持。 ARM 为 NEON 向量提供强制转换,例如 vreinterpretq_u64_u8。函数上的inline 需要C99。

#include <msa.h>
#include <stdint.h>
#include <string.h>

inline v4i32 reinterpretq_i32_u32(const v4u32 val) 
    v4i32 res;
    memcpy(&res, &val, sizeof(res));
    return res;


inline v4u32 reinterpretq_u32_i32(const v4i32 val) 
    v4u32 res;
    memcpy(&res, &val, sizeof(res));
    return res;


#define ALIGN16 __attribute__((aligned(16)))

int main(int argc, char* argv[])

    ALIGN16 uint32_t a[] = 64, 128, 256, 512;
    ALIGN16 uint32_t b[] = 1024, 2048, 4096, 8192;
    ALIGN16 uint32_t c[4];

    v4u32 va = reinterpretq_u32_i32(__builtin_msa_ld_w (a, 0));
    v4u32 vb = reinterpretq_u32_i32(__builtin_msa_ld_w (b, 0));

    v4u32 vc = __builtin_msa_adds_u_w (va, vb);
    __builtin_msa_st_w (reinterpretq_i32_u32(vc), c, 0);

    return 0;

-O3 进行编译(-Wall -Wextra 是干净的):

MSA$ mips-img-linux-gnu-gcc.exe -O3 -mmsa test.c -c
MSA$

并且反汇编看起来通过了嗅探测试:

MSA$ mips-img-linux-gnu-objdump.exe --disassemble test.o

test.o:     file format elf32-tradbigmips

Disassembly of section .text:

00000000 <main>:
   0:   27bdffc8        addiu      sp,sp,-56
   4:   3c020000        lui        v0,0x0
   8:   24420000        addiu      v0,v0,0
   c:   78001062        ld.w       $w1,0(v0)
  10:   3c020000        lui        v0,0x0
  14:   24420000        addiu      v0,v0,0
  18:   78001022        ld.w       $w0,0(v0)
  1c:   79c10010        adds_u.w   $w0,$w0,$w1
  20:   7802e826        st.w       $w0,8(sp)
  24:   93a2000b        lbu        v0,11(sp)
  28:   03e00009        jr         ra
  2c:   27bd0038        addiu      sp,sp,56

为了完整起见,GCC 6.3.0:

MSA$ mips-img-linux-gnu-gcc.exe --version
mips-img-linux-gnu-gcc.exe (Codescape GNU Tools 2017.10-05 for MIPS IMG Linux) 6.3.0
Copyright (C) 2016 Free Software Foundation, Inc.

【讨论】:

我想知道使用typedef char msa128 __attribute__((vector_size (16), aligned (16))); 作为基本向量类型是否足够。由于它是字符类型,GCC 允许它为任何其他类型起别名,因此您可以在例如v2u64 a;v4f32 b; 使用 a = (v2u64)((msa128)(b));b = (v4f32)((msa128)(a));(在 g++ (-std=gnu++14) 和 gcc (-std=gnu11) 中使用 GCC 5.4.0)。

以上是关于使用 __builtin_msa_ld_* 后如何转换为无符号向量类型的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 mongoDb java 异步驱动程序插入 mongoDb 集合后获取 _id

paypal支付平台如何使用二次验证码_虚拟MFA_两步验证_谷歌身份验证器?

__future__ 模块在 Python 2.7 中如何工作? [复制]

[Java]_[初级]_[Observer和Observable失效后如何使用java.beans包下的类来实现观察者模式]

SBJson 解析后的执行 (__NSArrayM objectForKey:)

cx_冻结。安装后如何安装服务和执行脚本