为什么Rust会以特定于体系结构的方式生成LLVM IR,并使用空数组作为可变静态变量?

问题描述 投票:4回答:1
struct Point<'a> {
    x: i32,
    caption: &'a str,
    y: i32,
}

static mut global_var: Point = Point {
    x: 123,
    y: 344,
    caption: "123",
};

相应的LLVM IR为:

%Point = type
{
    [0 x i64],
    { [0 x i8]*, i64 },
    [0 x i32],
    i32,
    [0 x i32],
    i32,
    [0 x i32]
}

@_ZN5hello10global_var17h76c725a117a5fdc6E = internal global
    <{ i8*, [16 x i8] }>
    <{
       i8* getelementptr inbounds
       (
           <{ [3 x i8] }>,
           <{ [3 x i8] }>* @6,
           i32 0,
           i32 0,
           i32 0
       ),
       [16 x i8] c"\03\00\00\00\00\00\00\00{\00\00\00X\01\00\00"
    }>,
    align 8,
    !dbg !330

我试图找到两个有趣的问题的答案:

  1. 为什么%Point的类型定义中没有空数组?这里似乎不是Pascal样式的数组。
  2. 为什么global_var用一种特定于体系结构的间接方式初始化(​​整数的内容直接用一个小字节序的缓冲区填充,因为LLVM IR代码在结构上是独立的?

如果可能,我们可以以更具可读性的样式获取带有这些初始化的LLVM IR代码吗?


更新以回应一些评论:

  1. 为什么c“ \ 03 \ 00 \ 00 \ 00 \ 00 \ 00 \ 00 \ 00 {\ 00 \ 00 \ 00X \ 01 \ 00 \ 00”是该结构的初始化?

[如果我们记下字符串的十六进制表示,我们将发现i64 3,i32 123和i23 344恰好存储在小端架构中。

  1. 我的锈版本是1.41.0-nightly(19a0de242 2019-12-12)。 LLVM IR由cargo rustc -- --emit=llvm-bc生成,然后使用llvm-dis进行分解。
arrays rust architecture llvm endianness
1个回答
2
投票

免责声明:这个答案是有根据的猜测。

TL; DR:我想显式的填充数组和显式的初始化是为了避免留下任何未初始化的字节,以及由此导致的未定义行为。


绕过C的路

意识到LLVM从C继承了许多低层语义很重要。毕竟,它的第一个也是最重要的前端是Clang,并且这已经构成了它的大部分。

[当Clang将struct降低到LLVM IR时,它肯定会离开LL​​VM找出填充link

因此:

    struct A
    {
        int a;
        struct { char const* ptr; size_t len; } str;
        char c;
    };

    A const GLOBAL{ 1, { "hello", 5 }, 'c' };

降低为:

%struct.A = type { i32, %struct.anon, i8 }
%struct.anon = type { i8*, i64 }


@_ZL6GLOBAL = internal constant %struct.A
{
    i32 1,
    %struct.anon
    {
        i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i32 0, i32 0),
        i64 5
    },
    i8 99
}, align 8

@.str = private unnamed_addr constant [6 x i8] c"hello\00", align 1

这意味着填充字节保持未初始化状态,并且以典型的C方式读取未初始化的字节是未定义的行为。

[这意味着用未初始化的填充字节进行位复制是未定义的行为,虽然memcpy调用(降为内在函数)似乎没有受到影响,但我不知道C标准中提供了[ C0]通过...


回到锈

只要出现未定义的行为,铁锈就站稳立场:

  • 安全Rust 1中不应有未定义的行为。
  • 在不安全的Rust中应该有尽可能少的Undefined Behavior。

[留下未初始化的填充字节,并让用户执行位复制,在很大程度上似乎是不必要的未定义行为来源:

似乎没有太多(如果有的话)性能优势:Rust可以自由地重新排列结构成员并压缩结构,因此填充字节通常很少(仅尾随几个)。

因此,我的猜测是rustc显式指定填充数组2并显式初始化它们,以避免遗留任何未初始化的填充字节。

1 还有。例如,由于LLVM考虑如果值不适合,将memcpy转换为float是UB,或者是LLVM考虑无副作用的无限循环是UB-两者都从C继承。正在进行中。

2 这不提供0大小的数组的原理,这些数组对我来说似乎完全多余。

© www.soinside.com 2019 - 2024. All rights reserved.