Skip to content

Commit 760e567

Browse files
committed
Auto merge of #122975 - DianQK:simplify_ub_check, r=saethlin
Eliminate `UbChecks` for non-standard libraries The purpose of this PR is to allow other passes to treat `UbChecks` as constants in MIR for optimization after #122629. r? RalfJung
2 parents db2f975 + 47ed73a commit 760e567

14 files changed

+119
-46
lines changed

compiler/rustc_feature/src/builtin_attrs.rs

+4
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
821821
rustc_allow_incoherent_impl, AttributeType::Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::No,
822822
"#[rustc_allow_incoherent_impl] has to be added to all impl items of an incoherent inherent impl."
823823
),
824+
rustc_attr!(
825+
rustc_preserve_ub_checks, AttributeType::CrateLevel, template!(Word), ErrorFollowing, EncodeCrossCrate::No,
826+
"`#![rustc_preserve_ub_checks]` prevents the designated crate from evaluating whether UB checks are enabled when optimizing MIR",
827+
),
824828
rustc_attr!(
825829
rustc_deny_explicit_impl,
826830
AttributeType::Normal,

compiler/rustc_middle/src/mir/syntax.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1361,8 +1361,8 @@ pub enum NullOp<'tcx> {
13611361
AlignOf,
13621362
/// Returns the offset of a field
13631363
OffsetOf(&'tcx List<(VariantIdx, FieldIdx)>),
1364-
/// Returns whether we want to check for UB.
1365-
/// This returns the value of `cfg!(debug_assertions)` at monomorphization time.
1364+
/// Returns whether we should perform some UB-checking at runtime.
1365+
/// See the `ub_checks` intrinsic docs for details.
13661366
UbChecks,
13671367
}
13681368

compiler/rustc_mir_transform/src/instsimplify.rs

+15
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
//! Performs various peephole optimizations.
22
33
use crate::simplify::simplify_duplicate_switch_targets;
4+
use rustc_ast::attr;
45
use rustc_middle::mir::*;
56
use rustc_middle::ty::layout;
67
use rustc_middle::ty::layout::ValidityRequirement;
78
use rustc_middle::ty::{self, GenericArgsRef, ParamEnv, Ty, TyCtxt};
9+
use rustc_span::sym;
810
use rustc_span::symbol::Symbol;
911
use rustc_target::abi::FieldIdx;
1012
use rustc_target::spec::abi::Abi;
@@ -22,10 +24,15 @@ impl<'tcx> MirPass<'tcx> for InstSimplify {
2224
local_decls: &body.local_decls,
2325
param_env: tcx.param_env_reveal_all_normalized(body.source.def_id()),
2426
};
27+
let preserve_ub_checks =
28+
attr::contains_name(tcx.hir().krate_attrs(), sym::rustc_preserve_ub_checks);
2529
for block in body.basic_blocks.as_mut() {
2630
for statement in block.statements.iter_mut() {
2731
match statement.kind {
2832
StatementKind::Assign(box (_place, ref mut rvalue)) => {
33+
if !preserve_ub_checks {
34+
ctx.simplify_ub_check(&statement.source_info, rvalue);
35+
}
2936
ctx.simplify_bool_cmp(&statement.source_info, rvalue);
3037
ctx.simplify_ref_deref(&statement.source_info, rvalue);
3138
ctx.simplify_len(&statement.source_info, rvalue);
@@ -140,6 +147,14 @@ impl<'tcx> InstSimplifyContext<'tcx, '_> {
140147
}
141148
}
142149

150+
fn simplify_ub_check(&self, source_info: &SourceInfo, rvalue: &mut Rvalue<'tcx>) {
151+
if let Rvalue::NullaryOp(NullOp::UbChecks, _) = *rvalue {
152+
let const_ = Const::from_bool(self.tcx, self.tcx.sess.opts.debug_assertions);
153+
let constant = ConstOperand { span: source_info.span, const_, user_ty: None };
154+
*rvalue = Rvalue::Use(Operand::Constant(Box::new(constant)));
155+
}
156+
}
157+
143158
fn simplify_cast(&self, rvalue: &mut Rvalue<'tcx>) {
144159
if let Rvalue::Cast(kind, operand, cast_ty) = rvalue {
145160
let operand_ty = operand.ty(self.local_decls, self.tcx);

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1590,6 +1590,7 @@ symbols! {
15901590
rustc_peek_maybe_init,
15911591
rustc_peek_maybe_uninit,
15921592
rustc_polymorphize_error,
1593+
rustc_preserve_ub_checks,
15931594
rustc_private,
15941595
rustc_proc_macro_decls,
15951596
rustc_promotable,

library/alloc/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@
176176
// Language features:
177177
// tidy-alphabetical-start
178178
#![cfg_attr(bootstrap, feature(associated_type_bounds))]
179+
#![cfg_attr(not(bootstrap), rustc_preserve_ub_checks)]
179180
#![cfg_attr(not(test), feature(coroutine_trait))]
180181
#![cfg_attr(test, feature(panic_update_hook))]
181182
#![cfg_attr(test, feature(test))]

library/core/src/intrinsics.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -2686,12 +2686,14 @@ pub const unsafe fn typed_swap<T>(x: *mut T, y: *mut T) {
26862686
unsafe { ptr::swap_nonoverlapping(x, y, 1) };
26872687
}
26882688

2689-
/// Returns whether we should perform some UB-checking at runtime. This evaluate to the value of
2690-
/// `cfg!(debug_assertions)` during monomorphization.
2691-
///
2692-
/// This intrinsic is evaluated after monomorphization, which is relevant when mixing crates
2693-
/// compiled with and without debug_assertions. The common case here is a user program built with
2694-
/// debug_assertions linked against the distributed sysroot which is built without debug_assertions.
2689+
/// Returns whether we should perform some UB-checking at runtime. This eventually evaluates to
2690+
/// `cfg!(debug_assertions)`, but behaves different from `cfg!` when mixing crates built with different
2691+
/// flags: if the crate has debug assertions enabled or carries the `#[rustc_preserve_ub_checks]`
2692+
/// attribute, evaluation is delayed until monomorphization (or until the call gets inlined into
2693+
/// a crate that does not delay evaluation further); otherwise it can happen any time.
2694+
///
2695+
/// The common case here is a user program built with debug_assertions linked against the distributed
2696+
/// sysroot which is built without debug_assertions but with `#[rustc_preserve_ub_checks]`.
26952697
/// For code that gets monomorphized in the user crate (i.e., generic functions and functions with
26962698
/// `#[inline]`), gating assertions on `ub_checks()` rather than `cfg!(debug_assertions)` means that
26972699
/// assertions are enabled whenever the *user crate* has debug assertions enabled. However if the

library/core/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
))]
9595
#![no_core]
9696
#![rustc_coherence_is_core]
97+
#![cfg_attr(not(bootstrap), rustc_preserve_ub_checks)]
9798
//
9899
// Lints:
99100
#![deny(rust_2021_incompatible_or_patterns)]

library/std/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@
221221
//
222222
#![cfg_attr(not(feature = "restricted-std"), stable(feature = "rust1", since = "1.0.0"))]
223223
#![cfg_attr(feature = "restricted-std", unstable(feature = "restricted_std", issue = "none"))]
224+
#![cfg_attr(not(bootstrap), rustc_preserve_ub_checks)]
224225
#![doc(
225226
html_playground_url = "https://play.rust-lang.org/",
226227
issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/",

tests/coverage/unreachable.cov-map

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
Function name: unreachable::UNREACHABLE_CLOSURE::{closure#0}
2-
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0f, 27, 00, 47]
1+
Function name: unreachable::UNREACHABLE_CLOSURE::{closure#0} (unused)
2+
Raw bytes (9): 0x[01, 01, 00, 01, 00, 0f, 27, 00, 47]
33
Number of files: 1
44
- file 0 => global file 1
55
Number of expressions: 0
66
Number of file 0 mappings: 1
7-
- Code(Counter(0)) at (prev + 15, 39) to (start + 0, 71)
7+
- Code(Zero) at (prev + 15, 39) to (start + 0, 71)
88

9-
Function name: unreachable::unreachable_function
10-
Raw bytes (9): 0x[01, 01, 00, 01, 01, 11, 01, 01, 25]
9+
Function name: unreachable::unreachable_function (unused)
10+
Raw bytes (9): 0x[01, 01, 00, 01, 00, 11, 01, 01, 25]
1111
Number of files: 1
1212
- file 0 => global file 1
1313
Number of expressions: 0
1414
Number of file 0 mappings: 1
15-
- Code(Counter(0)) at (prev + 17, 1) to (start + 1, 37)
15+
- Code(Zero) at (prev + 17, 1) to (start + 1, 37)
1616

1717
Function name: unreachable::unreachable_intrinsic (unused)
1818
Raw bytes (9): 0x[01, 01, 00, 01, 00, 16, 01, 01, 2c]

tests/mir-opt/inline/unwrap_unchecked.unwrap_unchecked.PreCodegen.after.panic-abort.mir

+2-11
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
1111
}
1212
scope 3 {
1313
scope 4 (inlined unreachable_unchecked) {
14-
let mut _3: bool;
15-
let _4: ();
1614
scope 5 {
1715
}
1816
scope 6 (inlined core::ub_checks::check_language_ub) {
@@ -26,23 +24,16 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
2624
bb0: {
2725
StorageLive(_2);
2826
_2 = discriminant(_1);
29-
switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb3];
27+
switchInt(move _2) -> [0: bb2, 1: bb1, otherwise: bb2];
3028
}
3129

3230
bb1: {
33-
StorageLive(_3);
34-
_3 = UbChecks();
35-
assume(_3);
36-
_4 = unreachable_unchecked::precondition_check() -> [return: bb3, unwind unreachable];
37-
}
38-
39-
bb2: {
4031
_0 = ((_1 as Some).0: T);
4132
StorageDead(_2);
4233
return;
4334
}
4435

45-
bb3: {
36+
bb2: {
4637
unreachable;
4738
}
4839
}

tests/mir-opt/inline/unwrap_unchecked.unwrap_unchecked.PreCodegen.after.panic-unwind.mir

+2-11
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
1111
}
1212
scope 3 {
1313
scope 4 (inlined unreachable_unchecked) {
14-
let mut _3: bool;
15-
let _4: ();
1614
scope 5 {
1715
}
1816
scope 6 (inlined core::ub_checks::check_language_ub) {
@@ -26,23 +24,16 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
2624
bb0: {
2725
StorageLive(_2);
2826
_2 = discriminant(_1);
29-
switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb3];
27+
switchInt(move _2) -> [0: bb2, 1: bb1, otherwise: bb2];
3028
}
3129

3230
bb1: {
33-
StorageLive(_3);
34-
_3 = UbChecks();
35-
assume(_3);
36-
_4 = unreachable_unchecked::precondition_check() -> [return: bb3, unwind unreachable];
37-
}
38-
39-
bb2: {
4031
_0 = ((_1 as Some).0: T);
4132
StorageDead(_2);
4233
return;
4334
}
4435

45-
bb3: {
36+
bb2: {
4637
unreachable;
4738
}
4839
}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//@ unit-test: InstSimplify
2+
//@ compile-flags: -Cdebug-assertions=no -Zinline-mir
3+
4+
// EMIT_MIR ub_check.unwrap_unchecked.InstSimplify.diff
5+
pub fn unwrap_unchecked(x: Option<i32>) -> i32 {
6+
// CHECK-LABEL: fn unwrap_unchecked(
7+
// CHECK-NOT: UbChecks()
8+
// CHECK: [[assume:_.*]] = const false;
9+
// CHECK-NEXT: assume([[assume]]);
10+
// CHECK-NEXT: unreachable_unchecked::precondition_check
11+
unsafe { x.unwrap_unchecked() }
12+
}
13+
14+
fn main() {
15+
unwrap_unchecked(None);
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
- // MIR for `unwrap_unchecked` before InstSimplify
2+
+ // MIR for `unwrap_unchecked` after InstSimplify
3+
4+
fn unwrap_unchecked(_1: Option<i32>) -> i32 {
5+
debug x => _1;
6+
let mut _0: i32;
7+
let mut _2: std::option::Option<i32>;
8+
scope 1 {
9+
scope 2 (inlined #[track_caller] Option::<i32>::unwrap_unchecked) {
10+
debug self => _2;
11+
let mut _3: isize;
12+
scope 3 {
13+
debug val => _0;
14+
}
15+
scope 4 {
16+
scope 5 (inlined unreachable_unchecked) {
17+
let mut _4: bool;
18+
let _5: ();
19+
scope 6 {
20+
}
21+
scope 7 (inlined core::ub_checks::check_language_ub) {
22+
scope 8 (inlined core::ub_checks::check_language_ub::runtime) {
23+
}
24+
}
25+
}
26+
}
27+
}
28+
}
29+
30+
bb0: {
31+
StorageLive(_2);
32+
_2 = _1;
33+
StorageLive(_3);
34+
StorageLive(_5);
35+
_3 = discriminant(_2);
36+
switchInt(move _3) -> [0: bb2, 1: bb3, otherwise: bb1];
37+
}
38+
39+
bb1: {
40+
unreachable;
41+
}
42+
43+
bb2: {
44+
StorageLive(_4);
45+
- _4 = UbChecks();
46+
+ _4 = const false;
47+
assume(_4);
48+
_5 = unreachable_unchecked::precondition_check() -> [return: bb1, unwind unreachable];
49+
}
50+
51+
bb3: {
52+
_0 = move ((_2 as Some).0: i32);
53+
StorageDead(_5);
54+
StorageDead(_3);
55+
StorageDead(_2);
56+
return;
57+
}
58+
}
59+

tests/mir-opt/pre-codegen/duplicate_switch_targets.ub_if_b.PreCodegen.after.mir

+1-10
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ fn ub_if_b(_1: Thing) -> Thing {
55
let mut _0: Thing;
66
let mut _2: isize;
77
scope 1 (inlined unreachable_unchecked) {
8-
let mut _3: bool;
9-
let _4: ();
108
scope 2 {
119
}
1210
scope 3 (inlined core::ub_checks::check_language_ub) {
@@ -17,7 +15,7 @@ fn ub_if_b(_1: Thing) -> Thing {
1715

1816
bb0: {
1917
_2 = discriminant(_1);
20-
switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb3];
18+
switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb2];
2119
}
2220

2321
bb1: {
@@ -26,13 +24,6 @@ fn ub_if_b(_1: Thing) -> Thing {
2624
}
2725

2826
bb2: {
29-
StorageLive(_3);
30-
_3 = UbChecks();
31-
assume(_3);
32-
_4 = unreachable_unchecked::precondition_check() -> [return: bb3, unwind unreachable];
33-
}
34-
35-
bb3: {
3627
unreachable;
3728
}
3829
}

0 commit comments

Comments
 (0)