ARM Neon:如何将uint8x16_t转换为uint8x8x2_t?

时间:2017-04-20 13:38:14

标签: c++ c arm vectorization neon

我最近发现了vreinterpret{q}_dsttype_srctype casting operator。但是,这似乎不支持this link(页面底部)中描述的数据类型的转换:

  

一些内在函数使用以下形式的矢量类型数组:

     

<type><size>x<number of lanes>x<length of array>_t

     

这些类型被视为包含单个的普通C结构   元素名为val。

     

示例结构定义是:

struct int16x4x2_t    
{
    int16x4_t val[2];     
};

您知道如何从uint8x16_t转换为uint8x8x2_t吗?

请注意,问题cannot be reliably addressed using union(从非活动成员读取会导致未定义的行为编辑:这只是C ++的情况,而事实证明C allows type punning),也不是using pointers to cast(打破严格的别名规则)。

4 个答案:

答案 0 :(得分:6)

在C ++中完全合法通过指针转换输入pun,只要你只对char*进行操作即可。这并非巧合,是memcpy被定义为正在进行的工作(技术上unsigned char*,这已经足够了)。

请遵守以下段落:

  

对于任何简单的对象(基类子对象除外)   可复制类型T,无论对象是否包含有效的类型值   T,构成对象的底层字节(1.7)可以复制到   char或unsigned char的数组。

     

42如果复制了char或unsigned char数组的内容   进入物体后,物体应随后保持其原始状态   值。 [实施例:

#define N sizeof(T)
char buf[N];
T obj;
// obj initialized to its original value
std::memcpy(buf, &obj, N);
// between these two calls to std::memcpy,
// obj might be modified 
std::memcpy(&obj, buf, N);
// at this point, each subobject of obj of scalar type
// holds its original value
     

- 结束示例]

简单地说,像这样复制是std::memcpy的预期功能。只要您处理的类型满足必要的琐碎要求,它就完全合法。

严格别名包括char*unsigned char* - 您可以使用这些别名为任何类型添加别名。

请注意,对于未签名的int,具体来说,这里有一些非常明确的余地。 C ++标准要求它们符合C标准的要求。 C标准规定了格式。陷阱表示或类似事物的唯一方法是,如果您的实现有任何填充位,但ARM没有任何8位字节,8位和16位整数。因此,对于零填充位实现的无符号整数,任何字节都是有效的无符号整数。

  

对于unsigned char以外的无符号整数类型,这些位   对象表示应分为两组:   值位和填充位(不需要任何值   后者)。如果有N个值位,则每个位应表示   1和2N-1之间的2的不同功率,使对象   该类型应能够代表0的值   使用纯二进制表示到2N-1;这应该是   称为价值表示。任何填充位的值都是   未指定的。

答案 1 :(得分:5)

根据您的评论,您似乎想要执行真正的转换 - 也就是说,要生成不同类型的独特,新的,单独的值。这与重新解释完全不同,例如您的问题的引入表明您想要的。特别是,你假设变量声明如下:

uint8x16_t  a;
uint8x8x2_t b;

// code to set the value of a ...

并且您想知道如何设置b的值,以便它在某种意义上等同于a的值。

对C语言说:

严格别名规则(C2011 6.5/7)表示,

  

对象的存储值只能由左值访问   具有以下类型之一的表达式:

     
      
  • 与对象的有效类型兼容的类型,[...]
  •   
  • 聚合或联合类型,其成员[...]或
  • 中包含上述类型之一   
  • 字符类型。
  •   

(强调补充。其他枚举选项涉及对象或兼容类型的有效类型的不同限定和不同签名的版本;这些在这里不相关。)

请注意,这些条款永远不会干扰访问a的值,包括成员值,通过变量a,同样适用于{{1} }。但是不要忽略忽视术语“有效类型”的用法。 - 这是在不同情况下可能会出现问题的地方。稍后会详细介绍。

使用联合

C当然允许您通过中间b执行转换,或者您可以首先依赖union作为联盟成员,以便删除&#34;中间&#34 ;部分:

b

使用类型转换指针(生成UB)

然而,尽管看起来并不常见,但C不允许你通过指针输入双关语:

union {
    uint8x16_t  x1;
    uint8x8_2_t x2;
} temp;
temp.x1 = a;
b = temp.x2;

在那里,您通过类型为// UNDEFINED BEHAVIOR - strict-aliasing violation b = *(uint8x8x2_t *)&a; // DON'T DO THAT 的左值访问其有效类型为a的{​​{1}}的值。请注意,它不是被禁止的演员,甚至,我也不认为,解除引用 - 它是读取取消引用的值,以便应用{{1的副作用运营商。

使用uint8x16_t

现在,uint8x8x2_t呢?这是它变得有趣的地方。 C允许通过字符类型的左值访问=memcpy()的存储值,虽然它的参数声明为类型memcpy(),但这是对{如何{的唯一合理解释{1}}有效。当然its description将其描述为复制字符。因此,执行

没有错
a

完成此操作后,您可以通过变量b自由访问void *的值,如前所述。这样做的某些方面可能会在更一般的背景下出现问题,但这里没有UB。

然而 ,将此与您希望将转换后的值放入动态分配空间的表面上相似的情况进行对比:

memcpy()

这可能有什么问题?它没有任何问题,只要它发生,但是如果你之后尝试访问memcpy(&b, &a, sizeof a); 的值,那么你就有了UB。为什么?因为b点的内存没有声明的类型,因此它的有效类型是最后存储在其中的任何内容的有效类型(如果它具有有效类型),包括如果通过bC2011 6.5/6)将该值复制到其中。因此,uint8x8x2_t *c = malloc(sizeof(*c)); memcpy(c, &a, sizeof a); 点后的对象在复制后具有有效类型*c,而表达式c具有类型memcpy();严格别名规则表示通过该左值访问该对象会产生UB。

答案 2 :(得分:2)

所以这里有一堆陷阱。这反映了C ++。

首先,您可以将简单的可复制数据转换为char*unsigned char* std::byte*,然后将其从一个位置复制到另一个位置。结果是定义的行为。字节值未指定

如果通过memcpy之类的值从一种类型的值到另一种类型执行此操作,则在访问目标类型时会导致未定义的行为,除非目标类型具有所有字节的有效值表示,或者如果编译器指定了两种类型的布局。

目标类型中可能存在“陷阱表示” - 导致机器异常的字节组合或类似的东西(如果被解释为该类型的值)。想象一个不使用IEEE浮点数的系统,以及在NaN或INF等上进行数学运算会导致段错误。

还存在对齐问题。

在C中,我认为通过工会打字是合法的,具有相似的资格。

最后请注意,在严格阅读标准的情况下,即使foo* pf = (foo*)malloc(sizeof(foo));是普通旧数据,foo也不是指向foo的指针。您必须在与对象进行交互之前创建对象,并且在自动存储之外创建对象的唯一方法是通过new或展示位置new。这意味着在记忆之前必须拥有目标类型的数据。

答案 3 :(得分:0)

  

您知道如何从uint8x16_t转换为uint8x8x2_t吗?

uint8x16_t input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
uint8x8x2_t output = { vget_low_u8(input), vget_high_u8(input) };

必须理解,使用霓虹灯内在函数,uint8x16_t表示一个16字节的寄存器。 uint8x8x2_t代表两个相邻的8字节寄存器。对于ARMv7,它们可能是同一件事(q0 == {d0,d1}),但对于ARMv8,寄存器布局是不同的。必须使用两个函数来获取(提取)单个16字节寄存器的低8个字节和高8个字节。铛编译器将根据上下文确定需要哪些指令。