从 bool (i1) 向量到 i8、i16 等的 LLVM 位转换是不是定义明确?

Posted

技术标签:

【中文标题】从 bool (i1) 向量到 i8、i16 等的 LLVM 位转换是不是定义明确?【英文标题】:Is LLVM bitcast from vector of bool (i1) to i8, i16, etc. well defined?从 bool (i1) 向量到 i8、i16 等的 LLVM 位转换是否定义明确? 【发布时间】:2020-05-06 23:27:21 【问题描述】:

在 LLVM 中,<8 x i1> 类型的值是否可以比特转换为 i8?如果是这样,预期的位顺序是多少? LLVM documentation on bitcast 对此并不明确。它提出的主张是

bitcast 指令将value 转换为类型ty2。它始终是 no-op cast,因为此转换不会更改任何位。转换的完成就像该值已存储到内存并以 ty2 类型读回一样。

顺便说一句,在邮件列表中,已澄清 no-op cast does not mean what it sounds like。回到手头的问题,我看到将<8 x i1> 比特转换为任何其他类型(不仅仅是i8)的问题是<8 x i1> 类型的值不能存储到内存中。我已经通过实验证实了这一点(代码不包括在内),它也是well-documented on the mailing list。由于存储<8 x i1> 类型的值会导致未定义的行为,因此规范“就好像该值已存储到内存并作为ty2 类型读回一样”意味着任何与<8 x i1> 之间的位转换都会导致未定义的行为。请注意,之前已询问过very similar question,但此问题的答案并未对此处提出的一般健全性问题提供令人满意的答案。上述问题的作者通过将<8 x i1> 比特转换为<1 x i8> 解决了这个问题,但是这种转换涉及<8 x i1> 类型的参数,所以我不相信它是正确的。

对于它的价值,在我自己使用 LLVM 的一些小测试中,我已经确认比特转换 <8 x i1>i8 有效。下面是一个函数,它一次测试 8 个 i16s 是否每个都等于 42。

; Filename is equality-8x16.ll
define void @equals42(<8 x i16>* %src0,i8* %dst0,i64 %len0)  ; i32()*
entry:
    %len = udiv exact i64 %len0, 8
    br label %cond
cond:
    %i = phi i64 [ 0, %entry ], [ %isucc, %loop ]
    %src = phi <8 x i16>* [ %src0, %entry ], [ %srcsucc, %loop ]
    %dst = phi i8* [ %dst0, %entry ], [ %dstsucc, %loop ]
    %cmp = icmp slt i64 %i, %len
    br i1 %cmp, label %loop, label %end
loop:
    %isucc = add i64 %i, 1
    %srcsucc = getelementptr <8 x i16>, <8 x i16>* %src, i64 1
    %dstsucc = getelementptr i8, i8* %dst, i64 1
    %val = load <8 x i16>, <8 x i16>* %src
    %bits = icmp eq <8 x i16> %val, <i16 42,i16 42,i16 42,i16 42,i16 42,i16 42,i16 42,i16 42>
    %res = bitcast <8 x i1> %bits to i8
    store i8 %res, i8* %dst
    br label %cond
end:
    ret void;

下面是一些调用它的 C 代码 (call-equality.c):

#include <stdio.h>
#include <stdint.h>

#define SZ 8

void equals42(void*,void*,int64_t);

/* Prints the highest bit first and the lowest bit last */
void printbits(uint8_t x)

    for(int i=sizeof(x)<<3; i; i--)
        putchar('0'+((x>>(i-1))&1));


int main()
  uint16_t a[SZ * 8] = 0;
  uint8_t b[8];
  a[1] = 42;
  a[15] = 42;
  equals42(a,b,SZ * 8);
  for(int i = 0; i < SZ; i++)
    printf("Index %d:",i);
    printbits(b[i]);
    printf("\n");
  

构建、链接和运行:

llc-9 -O3 -mcpu=skylake -filetype=obj equality-8x16.ll
gcc call-equality.c equality-8x16.o
./a.out

结果如下:

Index 0:00000010
Index 1:10000000
Index 2:00000000
Index 3:00000000
Index 4:00000000
Index 5:00000000
Index 6:00000000
Index 7:00000000

这行得通,它甚至恰好符合我的预期。位置 1 和 15 的这些位被设置(将字节 1,位位置 7 解释为位位置 15)。但是,尚不清楚我是否会在大端平台上获得相同的结果(我使用的是小端 Skylake CPU)。再次强调,LLVM 的官方文档并没有记录涉及&lt;8 x i1&gt; 的比特广播的行为。

问题不仅仅是“这是否适用于您的计算机或我的计算机”。 (虽然如果有人有一个大端平台,我很想看看示例程序是否给出相同的结果)。真正的问题是:

是否有一些准权威的来源,即使它只是邮件列表线程和问题跟踪器,指定这些比特广播的语义? 如果这些比特广播不正确,将&lt;8 x i1&gt; 转换为i8 的惯用方法是什么?可以单独投影所有八位(通过extractelement),然后使用一些 OR 和位移构建一个i8,但这看起来既乏味又严重依赖优化传递来获得我所期望的随机操作。有更好的吗?

【问题讨论】:

【参考方案1】:

到目前为止,我发现的最接近的是 2018 年的 a mailing list thread,其中用户注意到 bitcast &lt;16 x i1&gt; %a1 to i16 优化不佳的问题。维护者回复,在r348104 中提供了修复(我在 GitHub 或 Phabricator 上找不到)。但这似乎暗示bitcast &lt;16 x i1&gt; %a1 to i16 被理解为定义明确。但它实际上应该是什么意思?元素 0 是否应该是结果字中的位位置 0?我想是的,但很高兴能在某个地方看到这一点。

【讨论】:

以上是关于从 bool (i1) 向量到 i8、i16 等的 LLVM 位转换是不是定义明确?的主要内容,如果未能解决你的问题,请参考以下文章

JVM笔记----从JVM角度浅谈 i++ 和 ++i 的区别

多个运算符匹配这些操作数

从 String 到 *const i8 的正确方法是啥?

如何从 C++ 中值的向量<bool> 构造整数值

Gym 101334D 记忆化dp

如何将对象向量从类返回到 main.cpp