Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eliminate UbChecks for non-standard libraries #122975

Merged
merged 1 commit into from Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Expand Up @@ -821,6 +821,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
rustc_allow_incoherent_impl, AttributeType::Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::No,
"#[rustc_allow_incoherent_impl] has to be added to all impl items of an incoherent inherent impl."
),
rustc_attr!(
rustc_preserve_ub_checks, AttributeType::CrateLevel, template!(Word), ErrorFollowing, EncodeCrossCrate::No,
"`#![rustc_preserve_ub_checks]` prevents the designated crate from evaluating whether UB checks are enabled when optimizing MIR",
),
rustc_attr!(
rustc_deny_explicit_impl,
AttributeType::Normal,
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_middle/src/mir/syntax.rs
Expand Up @@ -1361,8 +1361,8 @@ pub enum NullOp<'tcx> {
AlignOf,
/// Returns the offset of a field
OffsetOf(&'tcx List<(VariantIdx, FieldIdx)>),
/// Returns whether we want to check for UB.
/// This returns the value of `cfg!(debug_assertions)` at monomorphization time.
/// Returns whether we should perform some UB-checking at runtime.
/// See the `ub_checks` intrinsic docs for details.
UbChecks,
}

Expand Down
15 changes: 15 additions & 0 deletions compiler/rustc_mir_transform/src/instsimplify.rs
@@ -1,10 +1,12 @@
//! Performs various peephole optimizations.

use crate::simplify::simplify_duplicate_switch_targets;
use rustc_ast::attr;
use rustc_middle::mir::*;
use rustc_middle::ty::layout;
use rustc_middle::ty::layout::ValidityRequirement;
use rustc_middle::ty::{self, GenericArgsRef, ParamEnv, Ty, TyCtxt};
use rustc_span::sym;
use rustc_span::symbol::Symbol;
use rustc_target::abi::FieldIdx;
use rustc_target::spec::abi::Abi;
Expand All @@ -22,10 +24,15 @@ impl<'tcx> MirPass<'tcx> for InstSimplify {
local_decls: &body.local_decls,
param_env: tcx.param_env_reveal_all_normalized(body.source.def_id()),
};
let preserve_ub_checks =
attr::contains_name(tcx.hir().krate_attrs(), sym::rustc_preserve_ub_checks);
for block in body.basic_blocks.as_mut() {
for statement in block.statements.iter_mut() {
match statement.kind {
StatementKind::Assign(box (_place, ref mut rvalue)) => {
if !preserve_ub_checks {
ctx.simplify_ub_check(&statement.source_info, rvalue);
}
ctx.simplify_bool_cmp(&statement.source_info, rvalue);
ctx.simplify_ref_deref(&statement.source_info, rvalue);
ctx.simplify_len(&statement.source_info, rvalue);
Expand Down Expand Up @@ -140,6 +147,14 @@ impl<'tcx> InstSimplifyContext<'tcx, '_> {
}
}

fn simplify_ub_check(&self, source_info: &SourceInfo, rvalue: &mut Rvalue<'tcx>) {
if let Rvalue::NullaryOp(NullOp::UbChecks, _) = *rvalue {
let const_ = Const::from_bool(self.tcx, self.tcx.sess.opts.debug_assertions);
let constant = ConstOperand { span: source_info.span, const_, user_ty: None };
*rvalue = Rvalue::Use(Operand::Constant(Box::new(constant)));
}
}

fn simplify_cast(&self, rvalue: &mut Rvalue<'tcx>) {
if let Rvalue::Cast(kind, operand, cast_ty) = rvalue {
let operand_ty = operand.ty(self.local_decls, self.tcx);
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Expand Up @@ -1572,6 +1572,7 @@ symbols! {
rustc_peek_maybe_init,
rustc_peek_maybe_uninit,
rustc_polymorphize_error,
rustc_preserve_ub_checks,
rustc_private,
rustc_proc_macro_decls,
rustc_promotable,
Expand Down
1 change: 1 addition & 0 deletions library/alloc/src/lib.rs
Expand Up @@ -176,6 +176,7 @@
// Language features:
// tidy-alphabetical-start
#![cfg_attr(bootstrap, feature(associated_type_bounds))]
#![cfg_attr(not(bootstrap), rustc_preserve_ub_checks)]
#![cfg_attr(not(test), feature(coroutine_trait))]
#![cfg_attr(test, feature(panic_update_hook))]
#![cfg_attr(test, feature(test))]
Expand Down
14 changes: 8 additions & 6 deletions library/core/src/intrinsics.rs
Expand Up @@ -2686,12 +2686,14 @@ pub const unsafe fn typed_swap<T>(x: *mut T, y: *mut T) {
unsafe { ptr::swap_nonoverlapping(x, y, 1) };
}

/// Returns whether we should perform some UB-checking at runtime. This evaluate to the value of
/// `cfg!(debug_assertions)` during monomorphization.
///
/// This intrinsic is evaluated after monomorphization, which is relevant when mixing crates
/// compiled with and without debug_assertions. The common case here is a user program built with
/// debug_assertions linked against the distributed sysroot which is built without debug_assertions.
/// Returns whether we should perform some UB-checking at runtime. This eventually evaluates to
/// `cfg!(debug_assertions)`, but behaves different from `cfg!` when mixing crates built with different
/// flags: if the crate has debug assertions enabled or carries the `#[rustc_preserve_ub_checks]`
/// attribute, evaluation is delayed until monomorphization (or until the call gets inlined into
/// a crate that does not delay evaluation further); otherwise it can happen any time.
///
/// The common case here is a user program built with debug_assertions linked against the distributed
/// sysroot which is built without debug_assertions but with `#[rustc_preserve_ub_checks]`.
/// For code that gets monomorphized in the user crate (i.e., generic functions and functions with
/// `#[inline]`), gating assertions on `ub_checks()` rather than `cfg!(debug_assertions)` means that
/// assertions are enabled whenever the *user crate* has debug assertions enabled. However if the
Expand Down
1 change: 1 addition & 0 deletions library/core/src/lib.rs
Expand Up @@ -94,6 +94,7 @@
))]
#![no_core]
#![rustc_coherence_is_core]
#![cfg_attr(not(bootstrap), rustc_preserve_ub_checks)]
//
// Lints:
#![deny(rust_2021_incompatible_or_patterns)]
Expand Down
1 change: 1 addition & 0 deletions library/std/src/lib.rs
Expand Up @@ -221,6 +221,7 @@
//
#![cfg_attr(not(feature = "restricted-std"), stable(feature = "rust1", since = "1.0.0"))]
#![cfg_attr(feature = "restricted-std", unstable(feature = "restricted_std", issue = "none"))]
#![cfg_attr(not(bootstrap), rustc_preserve_ub_checks)]
#![doc(
html_playground_url = "https://play.rust-lang.org/",
issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/",
Expand Down
12 changes: 6 additions & 6 deletions tests/coverage/unreachable.cov-map
@@ -1,18 +1,18 @@
Function name: unreachable::UNREACHABLE_CLOSURE::{closure#0}
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0f, 27, 00, 47]
Function name: unreachable::UNREACHABLE_CLOSURE::{closure#0} (unused)
Raw bytes (9): 0x[01, 01, 00, 01, 00, 0f, 27, 00, 47]
Number of files: 1
- file 0 => global file 1
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Counter(0)) at (prev + 15, 39) to (start + 0, 71)
- Code(Zero) at (prev + 15, 39) to (start + 0, 71)

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

Function name: unreachable::unreachable_intrinsic (unused)
Raw bytes (9): 0x[01, 01, 00, 01, 00, 16, 01, 01, 2c]
Expand Down
Expand Up @@ -11,8 +11,6 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
}
scope 3 {
scope 4 (inlined unreachable_unchecked) {
let mut _3: bool;
let _4: ();
scope 5 {
}
scope 6 (inlined core::ub_checks::check_language_ub) {
Expand All @@ -26,23 +24,16 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
bb0: {
StorageLive(_2);
_2 = discriminant(_1);
switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb3];
switchInt(move _2) -> [0: bb2, 1: bb1, otherwise: bb2];
}

bb1: {
StorageLive(_3);
_3 = UbChecks();
assume(_3);
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
_4 = unreachable_unchecked::precondition_check() -> [return: bb3, unwind unreachable];
}

bb2: {
_0 = ((_1 as Some).0: T);
StorageDead(_2);
return;
}

bb3: {
bb2: {
unreachable;
}
}
Expand Up @@ -11,8 +11,6 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
}
scope 3 {
scope 4 (inlined unreachable_unchecked) {
let mut _3: bool;
let _4: ();
scope 5 {
}
scope 6 (inlined core::ub_checks::check_language_ub) {
Expand All @@ -26,23 +24,16 @@ fn unwrap_unchecked(_1: Option<T>) -> T {
bb0: {
StorageLive(_2);
_2 = discriminant(_1);
switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb3];
switchInt(move _2) -> [0: bb2, 1: bb1, otherwise: bb2];
}

bb1: {
StorageLive(_3);
_3 = UbChecks();
assume(_3);
_4 = unreachable_unchecked::precondition_check() -> [return: bb3, unwind unreachable];
}

bb2: {
_0 = ((_1 as Some).0: T);
StorageDead(_2);
return;
}

bb3: {
bb2: {
unreachable;
}
}
16 changes: 16 additions & 0 deletions tests/mir-opt/instsimplify/ub_check.rs
@@ -0,0 +1,16 @@
//@ unit-test: InstSimplify
//@ compile-flags: -Cdebug-assertions=no -Zinline-mir

// EMIT_MIR ub_check.unwrap_unchecked.InstSimplify.diff
pub fn unwrap_unchecked(x: Option<i32>) -> i32 {
// CHECK-LABEL: fn unwrap_unchecked(
// CHECK-NOT: UbChecks()
// CHECK: [[assume:_.*]] = const false;
// CHECK-NEXT: assume([[assume]]);
// CHECK-NEXT: unreachable_unchecked::precondition_check
unsafe { x.unwrap_unchecked() }
}

fn main() {
unwrap_unchecked(None);
}
@@ -0,0 +1,59 @@
- // MIR for `unwrap_unchecked` before InstSimplify
+ // MIR for `unwrap_unchecked` after InstSimplify

fn unwrap_unchecked(_1: Option<i32>) -> i32 {
debug x => _1;
let mut _0: i32;
let mut _2: std::option::Option<i32>;
scope 1 {
scope 2 (inlined #[track_caller] Option::<i32>::unwrap_unchecked) {
debug self => _2;
let mut _3: isize;
scope 3 {
debug val => _0;
}
scope 4 {
scope 5 (inlined unreachable_unchecked) {
let mut _4: bool;
let _5: ();
scope 6 {
}
scope 7 (inlined core::ub_checks::check_language_ub) {
scope 8 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
}
}
}
}

bb0: {
StorageLive(_2);
_2 = _1;
StorageLive(_3);
StorageLive(_5);
_3 = discriminant(_2);
switchInt(move _3) -> [0: bb2, 1: bb3, otherwise: bb1];
}

bb1: {
unreachable;
}

bb2: {
StorageLive(_4);
- _4 = UbChecks();
+ _4 = const false;
assume(_4);
_5 = unreachable_unchecked::precondition_check() -> [return: bb1, unwind unreachable];
}

bb3: {
_0 = move ((_2 as Some).0: i32);
StorageDead(_5);
StorageDead(_3);
StorageDead(_2);
return;
}
}

Expand Up @@ -5,8 +5,6 @@ fn ub_if_b(_1: Thing) -> Thing {
let mut _0: Thing;
let mut _2: isize;
scope 1 (inlined unreachable_unchecked) {
let mut _3: bool;
let _4: ();
scope 2 {
}
scope 3 (inlined core::ub_checks::check_language_ub) {
Expand All @@ -17,7 +15,7 @@ fn ub_if_b(_1: Thing) -> Thing {

bb0: {
_2 = discriminant(_1);
switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb3];
switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb2];
}

bb1: {
Expand All @@ -26,13 +24,6 @@ fn ub_if_b(_1: Thing) -> Thing {
}

bb2: {
StorageLive(_3);
_3 = UbChecks();
assume(_3);
_4 = unreachable_unchecked::precondition_check() -> [return: bb3, unwind unreachable];
}

bb3: {
unreachable;
}
}