前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Rust Stable 1.45 中的 神奇 Bug 解读

Rust Stable 1.45 中的 神奇 Bug 解读

作者头像
张汉东
发布于 2020-07-28 07:43:33
发布于 2020-07-28 07:43:33
74700
代码可运行
举报
文章被收录于专栏:Rust 编程Rust 编程
运行总次数:0
代码可运行

今天,Rust 官方仓库里报告了一个神奇的Bug,该 Bug 似乎动摇了 Rust 的世界法则,让我们一探究竟到底是否如此。

Bug 描述

今天,Rust 官方仓库里报告了一个神奇的Bug,该 Issues 编号为:

#74739 https://github.com/rust-lang/rust/issues/74739

目前该 Bug 存在于Rust Stable 1.44 和 1.45 版本中。下周即将发布的1.45.1 会修复此 Bug。该 Bug 在 Nightly 下已被修复。

该 Bug 的表现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct Foo {
    x: i32,
}

fn main() {
    let mut foo = Foo { x: 42 };
    let x = &mut foo.x;
    *x = 13;
    let y = foo;
    println!("{}", y.x); // -> 42; expected result: 13
}

正常情况下,最后的 y.x 应该输出 “13”,但是现在还是 “42”。这个结果意味着,代码第7行的可变引用并没有起到作用。

是不是很神奇?这个 Bug 让人感觉 Rust 世界的基本法则都崩塌了。

所以,这也激发了我的好奇心,就想一探究竟这个 Bug 到底是什么原因导致的,它到底是不是 Rust 世界法则的崩塌呢?

探索

该 Bug 既然在 Nightly 中已经被修复,那么可以先观察一下在 Stable 和 Nightly 中生成的 MIR (中级中间语言)有什么不同。

在 Playground 上选择 Stable 生成 MIR 查看如下:

图中右侧为输出的 MIR,红框里是关键的几个变量: "_1" 是变量 foo,"_2" 是变量 x,"_3" 是变量 y。

目测,最后 y 的值,被直接赋予了最初 "_1" 被赋予的 常量结构体实例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
_1 = const Foo { x: 42i32 };
...
(*_2) = const 13i32;  
...
_3 = const Foo { x: 42i32 };

接下来再看看 Nightly 下的 MIR 输出:

看的出来,和 stable 下生成的 MIR 不同的地方就在于:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
_1 = const Foo { x: 42i32 };
...
(*_2) = const 13i32;  
...
_3 = move _1;

其实,字段 x 的值都被修改过了,只是在 Nightly 下,是直接把修改后 foo 移动给了 y。而在 Stable 下,则是把原始的那个常量结构体实例,又拷贝了过去。

所以,问题就来了,难道是因为Stable下不识别 Move ?那继续实验一下。

将代码修改如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#[derive(Copy, Clone)]
struct Foo {
    x: i32,
}

fn main() {
    let mut foo = Foo { x: 42 };
    let x = &mut foo.x;
    *x = 13;
    let y = foo;
    println!("{}", y.x); // -> 42; expected result: 13
}

主要是给结构体 Foo 实现了 Copy trait,这样 Foo 的结构体实例就不会被 Move 了。我们依次查看一下修改后的代码输出的 MIR 差异。

在 Stable 下,输出依然不变。

但是在 Nightly 下变了,并没有发生 Move。

这个结果虽然符合我们的预期,但是在 Stable 下还是存在那个 Bug,所以,该 Bug 的原因跟结构体是否可以 Move 没有关系。

那么到底是什么原因导致的呢?

Bug 产生根源

再次回头审视两次 Stable 下输出的 MIR ,发现 Bug 的直接原因就是因为 y 在赋值的时候,本来应该是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
_1 = const Foo { x: 42i32 };
...
(*_2) = const 13i32;  
...
_3 = move _1;
// 或者
_3 = _1;

结果被改为了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
_1 = const Foo { x: 42i32 };
...
(*_2) = const 13i32;  
...
_3 = const Foo { x: 42i32 };

感觉这个常量实例是被编译器给强制分发到了需要 "_1" 的位置。

为什么会这样呢?我再次打开了 #74739 Issues,看到下面有两个已经被合并的相关Issues 和 PR 。

解释一下:

上面的 Issues 是说,在 #71911 的 PR(该 PR 是对 Const Prop 的重构和整理)中引入了一个问题,导致了 Miri 中的一个测试中的断言失效了。该断言正好是为了测试 "常量传播(const propagator )“ 功能对引用的正确跟踪。

所以上面那个 PR,就是为了修复这个问题。

看来,这个神奇的 Bug 就是和常量传播这个功能有直接关系的。

然而,在修复这个问题的过程中,正好错过了 Beta 测试的截止期,于是,Bug就被引入到了 Stable 1.44 和 1.45 中。

到此为止,我们终于搞清楚了这个 Bug 的根源。看来,这个 Bug 其实并不是我前面所担心的 Rust 世界法则的崩塌,是我多虑了。

话说回来,这个常量传播到底是做什么的呢?

常量传播

常量传播(Const Propagator)实际上一种编译器优化技术。常量传播的目的在于充分利用代码中存在的常量,将变量的使用替换为对常量的引用,并尽可能地去计算常量表达式。

比如上面的 Stable 输出的 MIR:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
_1 = const Foo { x: 42i32 };
...
(*_2) = const 13i32;  
...
_3 = const Foo { x: 42i32 };

只要是使用了 "_1" 则到处被替换为了结构体实例常量,这就是一种常量传播。只不过这里常量并没有传播到正确的位置,从而导致了这个 Bug。

Rust 官方在 2019年12月初公布了编译器引入了常量传播技术,用来优化 MIR 。相关链接:https://blog.rust-lang.org/inside-rust/2019/12/02/const-prop-on-by-default.html

目前,Rust 编译器能做到的程度如下:

1. 编译器可以自动识别代码中的常量,即便你没有显式声明。比如下面代码所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct Point {
  x: u32,
  y: u32,
}

let a = 2 + 2; // optimizes to 4
let b = [0, 1, 2, 3, 4, 5][3]; // optimizes to 3
let c = (Point { x: 21, y: 42 }).y; // optimizes to 42

上面的结构体 Point 在实例化的时候赋值为数字字面量,这个可以作为常量,那么编译器就会自动进行优化。

2. 消除控制流。比如下面代码所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const Foo: Option<u8> = Some(12);

let x = match Foo {
   None => panic!("no value"),
   Some(v) => v,
};
// 优化为
const Foo: Option<u8> = Some(12);
let x = 12;

这个非常有用。再比如,进行科学计算的时候:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let x = 2 + 4 * 6;

这行代码,实际上会生成很多检查:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let (_tmp0, overflowed) = CheckedMultiply(4, 6);
assert!(!overflowed, "attempt to multiply with overflow");

let (_tmp1, overflowed) = CheckedAdd(_tmp0, 2);
assert!(!overflowed, "attempt to add with overflow");

let x = _temp1;

这就增加了很多控制流。常量传播可以先将其简化为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let _tmp0 = 24;
assert!(!false, "attempt to multiply with overflow");

let _tmp1 = 26;
assert!(!false, "attempt to add with overflow");

let x = 26;

再进一步简化为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let x = 26;

减少Rust编译器处理的控制流数量会极大减少编译时间。 官方在 Debug 和 Release 模式下的各种测试用例的改进达到了 2-10%。 实例化的泛型函数的具体实例越多,这种优化的收益就越大。

总的来说,常量传播对于优化 MIR 有很积极的作用。

Bug 影响范围

有人担心,这个 Bug 会影响到很多真实世界的 Rust 代码。但是经过我们上面的分析,其实真实世界几乎不会触发这个 Bug 。因为触发这个 Bug,需要将值都设为常量,并且在它们之间不存在任何控制流或函数调用才行。

但是,还需要在下周 1.45.1 Stable 发布以后尽快升级。

小结

1. 该 Bug 看似违反了 Rust 的世界法则,其实不然。值是已经改变了,只不过因为常量传播而导致了问题。

2. 该 Bug 之所以能引入到 Stable,是因为测试用例失效,在修改这个问题的过程中,错过了 Beta 的截止日期,导致了 Bug 流入了 Stable。我们都知道, Rust的稳定流程是:Nightly -> Beta -> Stable。

3. 常量传播是为了进一步优化 MIR,可以对降低编译时间产生积极影响。

4. Rust 自身也是软件,产生 Bug 很正常。有人的地方就有 Bug,但是对于这种和 Rust 世界法则有悖的 Bug,还是需要刨根问底。

全文完,感谢阅读。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 觉学社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Rust日报】2020-07-26 - Easy Rust 让Rust文档更容易理解
今天Rust 官方仓库里报告了一个神奇的Bug,该 Bug 似乎动摇了 Rust 的世界法则,该 Bug 的表现如下:
MikeLoveRust
2020/07/28
4470
Rust项目中的Labels
按照issue数量从多到少排序: https://github.com/rust-lang/rust/labels?page=2&sort=count-desc,仅列出前几页
fliter
2024/02/26
1580
Rust项目中的Labels
【译】为 嵌入式 C 程序员编写的 Rust 指南
这是来自 Google OpenTitan 团队,给嵌入式 C 程序员专门打造的一份 Rust 指南。
张汉东
2021/10/13
5.2K0
【Rust日报】2020-07-30 fixed_vec减少Rust数组冗余边界检查
rust的Vec在使用索引的时候总会触发边界检查,在某些时候降低了程序的性能。通常解决方法是尽可能使用迭代器来处理数组。
MikeLoveRust
2020/08/04
9430
【Rust每周一知】如何理解Rust中的可变与不可变?
Rust的所有权(ownership)机制规定:Rust中的每个值都有一个被称为其所有者(owner)的变量,并且有且只能有唯一的所有者。
MikeLoveRust
2019/12/25
2K0
Rust入门之严谨如你
Rust作为一门快速发展的编程语言,已经在很多知名项目中使用,如firecracker、libra、tikv,包括Windows和Linux都在考虑Rust【1】。其中很重要的因素便是它的安全性和性能,这方面特性使Rust非常适合于系统编程。
Radar3
2020/11/25
1.8K2
Rust 1.50 稳定版发布解读
2021 年 2 月 11 号,Rust 1.50 稳定版发布[1]。1.50版更新包括:
张汉东
2021/03/16
7460
对 王垠《对 Rust 语言的分析》的分析
当时觉得这篇文章对 Rust 语言的分析太偏颇,但是王垠说这篇文章会一直更新。这几年也有不少新手在群里引用王垠这篇文章对 Rust 的看法,或者直接问我,我原以为过去五年了,王垠应该对文章里对观点有所更新吧,然而并没有。
张汉东
2021/07/14
2.3K2
Rust 外刊评论
Cranelift 是字节码联盟的原生代码编译器,作为Wasmtime和Lucet WebAssembly 虚拟机的基础,也用于其他环境,例如作为Rust 编译器的替代后端。
张汉东
2022/01/23
8450
【Rust日报】 2019-08-14:在Facebook上反复出现的 C++ bug
Repo: https://github.com/sivadeilra/vec_option
MikeLoveRust
2019/08/19
8360
深入 Rust 1.63 新特性 Scoped Thread
Scoped Thread 对应的是一种叫做结构化并发(Structured Concurrency)概念的实现。
张汉东
2022/12/08
1.8K0
听GPT 讲Rust源代码--compiler(26)
在Rust源代码中的rust/compiler/rustc_target/src/abi/call/mips.rs文件是关于MIPS架构的函数调用ABI(Aplication Binary Interface)定义。ABI是编程语言与底层平台之间的接口规范,用于定义函数调用、参数传递和异常处理等细节。
fliter
2024/04/01
980
听GPT 讲Rust源代码--compiler(26)
听GPT 讲Rust源代码--compiler(10)
在Rust源代码中,instsimplify.rs这个文件的作用是实现一系列用于简化MIR(Mid-level Intermediate Representation,中间级中间表示)指令的转换器。
fliter
2024/03/18
1100
听GPT 讲Rust源代码--compiler(10)
Rust生态安全漏洞总结系列 | Part 2
本系列主要是分析`RustSecurity` 安全数据库库[1]中记录的Rust生态社区中发现的安全问题,从中总结一些教训,学习Rust安全编程的经验。
张汉东
2021/06/10
8170
rust 上手很难?搞懂这些知识,前端开发能快速成为 rust 高手
在我的交流群里有许多人在讨论 rust。所以陆续有人开始尝试学习 rust,不过大家的一致共识就是:rust 上手很困难。当然,这样的共识在网上也普遍存在。
用户6901603
2024/03/20
1.4K1
rust 上手很难?搞懂这些知识,前端开发能快速成为 rust 高手
Rust 1.51.0 稳定版本改进介绍
上午查阅 Rust 官网内部博客,看到 Rust 1.51.0 stable 预发布版本已经开放测试。正式发布版本定于 UTC 标准时 2021-03-25,北京时间估计要到本周五。
niqin.com
2022/06/30
8440
Rust 1.51.0 稳定版本改进介绍
一名Java开发的Rust学习笔记
笔者的主力语言是Java,近三年Kotlin、Groovy、Go、TypeScript写得比较多。早年间还写过一些Python和JavaScript。总得来说落地在生产中的语言都是应用级语言,对于系统编程级语言接触不多。但这不妨碍我写下这么一篇笔记,说不定也有一些常年在应用层的同学想领略一下Rust的风采呢。
泊浮目
2024/03/19
2550
一名Java开发的Rust学习笔记
Rust 1.37.0 稳定版发布
Rust 1.37.0 stable 有什么?Rust 1.37.0 的亮点包括通过类型别名引用枚举变量、内置 cargo vendor、对宏使用未命名的 const、配置文件引导的优化、Cargo 中的 default-run 和枚举上的 #[repr(align(N))] 。
Debian中国
2020/01/21
8130
Rust那些事之并发Send与Sync
Send与Sync在Rust中属于marker trait,代码位于marker.rs,在标记模块中还有Copy、Unpin等trait。
公众号guangcity
2023/02/28
7290
Rust那些事之并发Send与Sync
听GPT 讲Rust源代码--compiler(46)
文件rust/compiler/rustc_codegen_ssa/src/traits/declare.rs的作用是定义了一个Declare trait,用于声明函数、变量和全局变量等需要使用的实体。
fliter
2024/04/26
1000
听GPT 讲Rust源代码--compiler(46)
相关推荐
【Rust日报】2020-07-26 - Easy Rust 让Rust文档更容易理解
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文