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

ARM Neon: How to convert from uint8x16_t to uint8x8x2_t?

我最近发现了 . However this doesn't seem to support conversion in the data type described at this link(页面底部):

Some intrinsics use an array of vector types of the form:

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

These types are treated as ordinary C structures containing a single element named val.

An example structure definition is:

struct int16x4x2_t    
{
    int16x4_t val[2];     
};

你知道如何将 uint8x16_t 转换为 uint8x8x2_t 吗?

请注意问题 (reading from inactive members leads to undefined behaviour Edit: That's only the case for C++, while it turns out that C allows type punning), nor by (违反了严格的别名规则)。

在 C++ 中 完全 通过指针转换输入双关语是合法的,只要您只对 char* 这样做。并非巧合的是,这就是 memcpy 所定义的工作(技术上 unsigned char* 这已经足够了)。

请注意以下段落:

For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array of char or unsigned char.

42 If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value. [Example:

#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

— end example ]

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

严格别名 包括 char*unsigned char* - 您可以自由地使用这些别名任何类型。

请注意,特别是对于无符号整数,您在这里有一些非常明确的回旋余地。 C++标准要求它们满足C标准的要求。 C 标准规定了格式。如果您的实现有任何填充位,但 ARM 没有任何 8 位字节、8 位和 16 位整数,则可以涉及陷阱表示或类似内容的唯一方法。因此,对于使用零填充位实现的无符号整数,任何字节都是有效的无符号整数。

For unsigned integer types other than unsigned char, the bits of the object representation shall be divided into two groups: value bits and padding bits (there need not be any of the latter). If there are N value bits, each bit shall represent a different power of 2 between 1 and 2N−1, so that objects of that type shall be capable of representing values from 0 to 2N−1 using a pure binary representation; this shall be known as the value representation. The values of any padding bits are unspecified.

根据您的评论,您似乎想要执行 善意的 转换 -- 即,生成不同类型的不同的、新的、独立的值。这与 重新解释 截然不同,例如您的问题的导入表明您想要。特别是,您可以这样声明变量:

uint8x16_t  a;
uint8x8x2_t b;

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

并且您想知道如何设置 b 的值,使其在某种意义上等同于 a.

的值

说到C语言:

严格的别名规则 (C2011 6.5/7) 说,

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

  • a type compatible with the effective type of the object, [...]
  • an aggregate or union type that includes one of the aforementioned types among its members [...], or
  • a character type.

(已强调。其他枚举选项涉及对象的有效类型或兼容类型的不同限定和不同签名版本;这些与此处无关。)

请注意,这些规定 从不 干扰通过变量 a 访问 a 的值,包括成员值,对于 b。但是不要忽视术语 "effective type" 的用法——这是在稍微不同的情况下事情可能会变得圆满的地方。稍后会详细介绍。

使用联合

C 当然允许您通过中间 union 执行转换,或者您可以依赖 b 首先成为联合成员,以便删除 "intermediate"部分:

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

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

然而,尽管它并不少见,但 C 不允许您通过指针进行双关语:

// UNDEFINED BEHAVIOR - strict-aliasing violation
    b = *(uint8x8x2_t *)&a;
// DON'T DO THAT

在那里,您正在通过 uint8x8x2_t 类型的左值访问 a 的值,其有效类型是 uint8x16_t。请注意,它不是被禁止的转换,甚至,我认为,取消引用——它是 读取 被取消引用的值,以便应用 [= 的副作用25=]运算符。

使用memcpy()

那么 memcpy() 呢?这就是它变得有趣的地方。 C 允许通过字符类型的左值访问 ab 的存储值,尽管它的参数被声明为 void * 类型,这是对如何 memcpy() 有效。当然its description定性为复制个字符。因此执行 a

没有错
memcpy(&b, &a, sizeof a);

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

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

uint8x8x2_t *c = malloc(sizeof(*c));
memcpy(c, &a, sizeof a);

这有什么问题吗?就目前而言,它没有任何问题,但是如果您随后尝试访问 *c 的值,那么您就会遇到 UB。为什么?因为 c 指向的内存没有声明类型,因此它的 有效类型 是最后存储在其中的任何内容的有效类型(如果它有有效类型),包括该值是否通过 memcpy() (C2011 6.5/6) 复制到其中。因此,c 指向的对象在复制后的有效类型为 uint8x16_t,而表达式 *c 的类型为 uint8x8x2_t;严格的别名规则表明通过该左值访问该对象会产生 UB。

所以这里有很多陷阱。这反映了 C++。

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

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

目标类型中有可能 "trap representations" -- 如果解释为该类型的值,会导致机器异常或类似情况的字节组合。想象一个不使用 IEEE 浮点数的系统,并且在 NaN 或 INF 等上进行数学计算会导致段错误。

还有对齐问题。

在 C 中,我相信通过联合进行类型双关是合法的,具有类似的资格。

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

Do you know how to convert from uint8x16_t to 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) };

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