Skip to content

Commit

Permalink
Auto merge of #129498 - saethlin:ptr-read-write-precondition, r=<try>
Browse files Browse the repository at this point in the history
Try enabling precondition checks on ptr::{read,write}

The overhead here is probably too much, but let's have the measurement anyway.

This will fail at least one codegen test.

r? `@ghost`

- [ ] Ralf says: If we keep this attribute, the docs on the intrinsic should be updated.
  • Loading branch information
bors committed Sep 3, 2024
2 parents d6c8169 + 2986cc0 commit 498c217
Show file tree
Hide file tree
Showing 13 changed files with 59 additions and 13 deletions.
4 changes: 4 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
rustc_no_mir_inline, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes,
"#[rustc_no_mir_inline] prevents the MIR inliner from inlining a function while not affecting codegen"
),
rustc_attr!(
rustc_no_ubchecks, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes,
"#[rustc_no_ubchecks] asks the compiler to delete UB checks from a function"
),
rustc_attr!(
rustc_intrinsic_must_be_overridden, Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::Yes,
"the `#[rustc_intrinsic_must_be_overridden]` attribute is used to declare intrinsics without real bodies",
Expand Down
28 changes: 23 additions & 5 deletions compiler/rustc_mir_transform/src/instsimplify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,32 @@ impl<'tcx> MirPass<'tcx> for InstSimplify {
}

fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let def_id = body.source.def_id();
let ctx = InstSimplifyContext {
tcx,
local_decls: &body.local_decls,
param_env: tcx.param_env_reveal_all_normalized(body.source.def_id()),
param_env: tcx.param_env_reveal_all_normalized(def_id),
};
let preserve_ub_checks =
attr::contains_name(tcx.hir().krate_attrs(), sym::rustc_preserve_ub_checks);
// FIXME(async_closures) tcx.has_attr on async closures seems to ICE. Not sure why.
let remove_ub_checks = if tcx.is_coroutine(def_id) {
false
} else {
tcx.has_attr(def_id, sym::rustc_no_ubchecks)
};
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);
if remove_ub_checks {
ctx.simplify_ub_check(&statement.source_info, rvalue, false);
} else if !preserve_ub_checks {
ctx.simplify_ub_check(
&statement.source_info,
rvalue,
tcx.sess.ub_checks(),
);
}
ctx.simplify_bool_cmp(&statement.source_info, rvalue);
ctx.simplify_ref_deref(&statement.source_info, rvalue);
Expand Down Expand Up @@ -199,9 +212,14 @@ impl<'tcx> InstSimplifyContext<'tcx, '_> {
}
}

fn simplify_ub_check(&self, source_info: &SourceInfo, rvalue: &mut Rvalue<'tcx>) {
fn simplify_ub_check(
&self,
source_info: &SourceInfo,
rvalue: &mut Rvalue<'tcx>,
ub_checks: bool,
) {
if let Rvalue::NullaryOp(NullOp::UbChecks, _) = *rvalue {
let const_ = Const::from_bool(self.tcx, self.tcx.sess.ub_checks());
let const_ = Const::from_bool(self.tcx, ub_checks);
let constant = ConstOperand { span: source_info.span, const_, user_ty: None };
*rvalue = Rvalue::Use(Operand::Constant(Box::new(constant)));
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1660,6 +1660,7 @@ symbols! {
rustc_never_returns_null_ptr,
rustc_never_type_options,
rustc_no_mir_inline,
rustc_no_ubchecks,
rustc_nonnull_optimization_guaranteed,
rustc_nounwind,
rustc_object_lifetime_default,
Expand Down
1 change: 1 addition & 0 deletions library/alloc/src/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,7 @@ impl<T: ?Sized, A: Allocator> Box<T, A> {
#[must_use = "losing the pointer will leak memory"]
#[unstable(feature = "allocator_api", issue = "32838")]
#[inline]
#[cfg_attr(not(bootstrap), rustc_no_ubchecks)]
pub fn into_raw_with_allocator(b: Self) -> (*mut T, A) {
let mut b = mem::ManuallyDrop::new(b);
// We carefully get the raw pointer out in a way that Miri's aliasing model understands what
Expand Down
1 change: 1 addition & 0 deletions library/core/src/mem/manually_drop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ impl<T> ManuallyDrop<T> {
#[must_use = "if you don't need the value, you can use `ManuallyDrop::drop` instead"]
#[stable(feature = "manually_drop_take", since = "1.42.0")]
#[inline]
#[cfg_attr(not(bootstrap), rustc_no_ubchecks)]
pub unsafe fn take(slot: &mut ManuallyDrop<T>) -> T {
// SAFETY: we are reading from a reference, which is guaranteed
// to be valid for reads.
Expand Down
1 change: 1 addition & 0 deletions library/core/src/mem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,7 @@ pub fn take<T: Default>(dest: &mut T) -> T {
#[must_use = "if you don't need the old value, you can just assign the new value directly"]
#[rustc_const_unstable(feature = "const_replace", issue = "83164")]
#[cfg_attr(not(test), rustc_diagnostic_item = "mem_replace")]
#[cfg_attr(not(bootstrap), rustc_no_ubchecks)]
pub const fn replace<T>(dest: &mut T, src: T) -> T {
// It may be tempting to use `swap` to avoid `unsafe` here. Don't!
// The compiler optimizes the implementation below to two `memcpy`s
Expand Down
4 changes: 2 additions & 2 deletions library/core/src/ptr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,7 @@ pub const unsafe fn swap_nonoverlapping<T>(x: *mut T, y: *mut T, count: usize) {
/// `swap_nonoverlapping` tries to use) so no need to manually SIMD it.
#[inline]
#[rustc_const_unstable(feature = "const_swap", issue = "83163")]
#[cfg_attr(not(bootstrap), rustc_no_ubchecks)]
const unsafe fn swap_nonoverlapping_simple_untyped<T>(x: *mut T, y: *mut T, count: usize) {
let x = x.cast::<MaybeUninit<T>>();
let y = y.cast::<MaybeUninit<T>>();
Expand Down Expand Up @@ -1425,7 +1426,6 @@ pub const unsafe fn read<T>(src: *const T) -> T {

// SAFETY: the caller must guarantee that `src` is valid for reads.
unsafe {
#[cfg(debug_assertions)] // Too expensive to always enable (for now?)
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"ptr::read requires that the pointer argument is aligned and non-null",
Expand Down Expand Up @@ -1634,7 +1634,7 @@ pub const unsafe fn write<T>(dst: *mut T, src: T) {
// `dst` cannot overlap `src` because the caller has mutable access
// to `dst` while `src` is owned by this function.
unsafe {
#[cfg(debug_assertions)] // Too expensive to always enable (for now?)
#[cfg(debug_assertions)]
ub_checks::assert_unsafe_precondition!(
check_language_ub,
"ptr::write requires that the pointer argument is aligned and non-null",
Expand Down
1 change: 0 additions & 1 deletion tests/codegen/mem-replace-big-type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// known to be `1` after inlining).

//@ compile-flags: -C no-prepopulate-passes -Zinline-mir=no
//@ ignore-debug: precondition checks in ptr::read make them a bad candidate for MIR inlining

#![crate_type = "lib"]

Expand Down
1 change: 0 additions & 1 deletion tests/codegen/mem-replace-simple-type.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//@ compile-flags: -O -C no-prepopulate-passes
//@ only-x86_64 (to not worry about usize differing)
//@ ignore-debug: precondition checks make mem::replace not a candidate for MIR inlining

#![crate_type = "lib"]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@ fn mem_replace(_1: &mut u32, _2: u32) -> u32 {
let mut _0: u32;
scope 1 (inlined std::mem::replace::<u32>) {
scope 2 {
scope 4 (inlined std::ptr::write::<u32>) {
scope 7 (inlined std::ptr::write::<u32>) {
scope 8 (inlined core::ub_checks::check_language_ub) {
scope 9 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
scope 10 (inlined align_of::<u32>) {
}
}
}
scope 3 (inlined std::ptr::read::<u32>) {
scope 4 (inlined core::ub_checks::check_language_ub) {
scope 5 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
scope 6 (inlined align_of::<u32>) {
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@ fn mem_replace(_1: &mut u32, _2: u32) -> u32 {
let mut _0: u32;
scope 1 (inlined std::mem::replace::<u32>) {
scope 2 {
scope 4 (inlined std::ptr::write::<u32>) {
scope 7 (inlined std::ptr::write::<u32>) {
scope 8 (inlined core::ub_checks::check_language_ub) {
scope 9 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
scope 10 (inlined align_of::<u32>) {
}
}
}
scope 3 (inlined std::ptr::read::<u32>) {
scope 4 (inlined core::ub_checks::check_language_ub) {
scope 5 (inlined core::ub_checks::check_language_ub::runtime) {
}
}
scope 6 (inlined align_of::<u32>) {
}
}
}

Expand Down
1 change: 0 additions & 1 deletion tests/mir-opt/pre-codegen/mem_replace.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// skip-filecheck
//@ compile-flags: -O -C debuginfo=0 -Zmir-opt-level=2 -Zinline-mir
//@ ignore-debug: precondition checks on ptr::read/write are under cfg(debug_assertions)
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY

#![crate_type = "lib"]
Expand Down
1 change: 0 additions & 1 deletion tests/mir-opt/pre-codegen/ptr_offset.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// skip-filecheck
//@ compile-flags: -O -C debuginfo=0 -Zmir-opt-level=2 -Zinline-mir
//@ ignore-debug: precondition checks are under cfg(debug_assertions)
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY

#![crate_type = "lib"]
Expand Down

0 comments on commit 498c217

Please sign in to comment.