Lower the assume intrinsic to a MIR statement

This commit is contained in:
Oli Scherer 2022-06-30 08:16:05 +00:00
parent 3c72788461
commit 3f07645120
33 changed files with 212 additions and 30 deletions

View File

@ -392,6 +392,7 @@ impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
| mir::StatementKind::AscribeUserType(..)
| mir::StatementKind::Coverage(..)
| mir::StatementKind::CopyNonOverlapping(..)
| mir::StatementKind::Assume(..)
| mir::StatementKind::Nop => {}
}
}

View File

@ -72,14 +72,14 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
self.consume_operand(location, dst);
self.consume_operand(location, count);
}
StatementKind::Nop
// Only relevant for mir typeck
StatementKind::AscribeUserType(..)
// Doesn't have any language semantics
| StatementKind::Coverage(..)
| StatementKind::AscribeUserType(..)
| StatementKind::Retag { .. }
| StatementKind::StorageLive(..) => {
// `Nop`, `AscribeUserType`, `Retag`, and `StorageLive` are irrelevant
// to borrow check.
}
// Takes a `bool` argument, and has no return value, thus being irrelevant for borrowck
| StatementKind::Assume(..)
// Does not actually affect borrowck
| StatementKind::StorageLive(..) => {}
StatementKind::StorageDead(local) => {
self.access_place(
location,
@ -88,7 +88,10 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
LocalMutationIsAllowed::Yes,
);
}
StatementKind::Deinit(..) | StatementKind::SetDiscriminant { .. } => {
StatementKind::Nop
| StatementKind::Retag { .. }
| StatementKind::Deinit(..)
| StatementKind::SetDiscriminant { .. } => {
bug!("Statement not allowed in this MIR phase")
}
}

View File

@ -599,14 +599,14 @@ impl<'cx, 'tcx> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtx
"Unexpected CopyNonOverlapping, should only appear after lower_intrinsics",
)
}
StatementKind::Nop
// Only relevant for mir typeck
StatementKind::AscribeUserType(..)
// Doesn't have any language semantics
| StatementKind::Coverage(..)
| StatementKind::AscribeUserType(..)
| StatementKind::Retag { .. }
| StatementKind::StorageLive(..) => {
// `Nop`, `AscribeUserType`, `Retag`, and `StorageLive` are irrelevant
// to borrow check.
}
// Takes a `bool` argument, and has no return value, thus being irrelevant for borrowck
| StatementKind::Assume(..)
// Does not actually affect borrowck
| StatementKind::StorageLive(..) => {}
StatementKind::StorageDead(local) => {
self.access_place(
location,
@ -616,7 +616,10 @@ impl<'cx, 'tcx> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtx
flow_state,
);
}
StatementKind::Deinit(..) | StatementKind::SetDiscriminant { .. } => {
StatementKind::Nop
| StatementKind::Retag { .. }
| StatementKind::Deinit(..)
| StatementKind::SetDiscriminant { .. } => {
bug!("Statement not allowed in this MIR phase")
}
}

View File

@ -1309,6 +1309,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
"Unexpected StatementKind::CopyNonOverlapping, should only appear after lowering_intrinsics",
),
StatementKind::FakeRead(..)
| StatementKind::Assume(..)
| StatementKind::StorageLive(..)
| StatementKind::StorageDead(..)
| StatementKind::Retag { .. }

View File

@ -791,6 +791,8 @@ fn codegen_stmt<'tcx>(
| StatementKind::Nop
| StatementKind::FakeRead(..)
| StatementKind::Retag { .. }
// We ignore `assume` intrinsics, they are only useful for optimizations
| StatementKind::Assume(..)
| StatementKind::AscribeUserType(..) => {}
StatementKind::Coverage { .. } => fx.tcx.sess.fatal("-Zcoverage is unimplemented"),

View File

@ -540,6 +540,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>(
return None;
} // conservative handling
StatementKind::Assign(_)
| StatementKind::Assume(_)
| StatementKind::FakeRead(_)
| StatementKind::SetDiscriminant { .. }
| StatementKind::Deinit(_)

View File

@ -357,9 +357,6 @@ fn codegen_regular_intrinsic_call<'tcx>(
let usize_layout = fx.layout_of(fx.tcx.types.usize);
match intrinsic {
sym::assume => {
intrinsic_args!(fx, args => (_a); intrinsic);
}
sym::likely | sym::unlikely => {
intrinsic_args!(fx, args => (a); intrinsic);

View File

@ -77,10 +77,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
let result = PlaceRef::new_sized(llresult, fn_abi.ret.layout);
let llval = match name {
sym::assume => {
bx.assume(args[0].immediate());
return;
}
sym::abort => {
bx.abort();
return;

View File

@ -93,6 +93,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
bx.memcpy(dst, align, src, align, bytes, crate::MemFlags::empty());
bx
}
mir::StatementKind::Assume(box ref op) => {
let op_val = self.codegen_operand(&mut bx, op);
bx.assume(op_val.immediate());
bx
}
mir::StatementKind::FakeRead(..)
| mir::StatementKind::Retag { .. }
| mir::StatementKind::AscribeUserType(..)

View File

@ -506,12 +506,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// These just return their argument
self.copy_op(&args[0], dest, /*allow_transmute*/ false)?;
}
sym::assume => {
let cond = self.read_scalar(&args[0])?.to_bool()?;
if !cond {
throw_ub_format!("`assume` intrinsic called with `false`");
}
}
sym::raw_eq => {
let result = self.raw_eq_intrinsic(&args[0], &args[1])?;
self.write_scalar(result, dest)?;

View File

@ -122,6 +122,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
self.copy_intrinsic(&src, &dst, &count, /* nonoverlapping */ true)?;
}
// Call Assume
Assume(box op) => {
let op = self.eval_operand(op, None)?;
let cond = self.read_scalar(&op)?.to_bool()?;
if !cond {
throw_ub_format!("`assume` called with `false`");
}
}
// Statements we do not track.
AscribeUserType(..) => {}

View File

@ -679,6 +679,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
| StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..)
| StatementKind::CopyNonOverlapping(..)
| StatementKind::Assume(..)
| StatementKind::Nop => {}
}
}

View File

@ -636,6 +636,15 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
);
}
}
StatementKind::Assume(box ref op) => {
let ty = op.ty(&self.body.local_decls, self.tcx);
if !ty.is_bool() {
self.fail(
location,
format!("`assume` argument must be `bool`, but got: `{}`", ty),
);
}
}
StatementKind::CopyNonOverlapping(box rustc_middle::mir::CopyNonOverlapping {
ref src,
ref dst,

View File

@ -1377,6 +1377,7 @@ impl Debug for Statement<'_> {
}) => {
write!(fmt, "copy_nonoverlapping(src={:?}, dst={:?}, count={:?})", src, dst, count)
}
Assume(box ref cond) => write!(fmt, "assume({:?})", cond),
Nop => write!(fmt, "nop"),
}
}

View File

@ -250,6 +250,7 @@ pub fn statement_kind_name(statement: &Statement<'_>) -> &'static str {
AscribeUserType(..) => "AscribeUserType",
Coverage(..) => "Coverage",
CopyNonOverlapping(..) => "CopyNonOverlapping",
Assume(..) => "Assume",
Nop => "Nop",
}
}

View File

@ -342,6 +342,14 @@ pub enum StatementKind<'tcx> {
/// I vaguely remember Ralf saying somewhere that he thought it should not be.
CopyNonOverlapping(Box<CopyNonOverlapping<'tcx>>),
/// Denotes a call to the intrinsic function `assume`.
///
/// The operand must be a boolean. Optimizers may use the value of the boolean to backtrack its
/// computation to infer information about other variables. So if the boolean came from a
/// `x < y` operation, subsequent operations on `x` and `y` could elide various bound checks.
/// If the argument is `false`, this operation is equivalent to `TerminatorKind::Unreachable`.
Assume(Box<Operand<'tcx>>),
/// No-op. Useful for deleting instructions without affecting statement indices.
Nop,
}

View File

@ -434,6 +434,9 @@ macro_rules! make_mir_visitor {
self.visit_operand(dst, location);
self.visit_operand(count, location)
}
StatementKind::Assume(box ref $($mutability)? val) => {
self.visit_operand(val, location)
}
StatementKind::Nop => {}
}
}

View File

@ -271,6 +271,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
| StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..)
| StatementKind::CopyNonOverlapping(..)
| StatementKind::Assume(..)
| StatementKind::Nop => None,
};
if let Some(destination) = destination {

View File

@ -143,6 +143,7 @@ impl<'mir, 'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tc
| StatementKind::Nop
| StatementKind::Retag(..)
| StatementKind::CopyNonOverlapping(..)
| StatementKind::Assume(..)
| StatementKind::StorageLive(..) => {}
}
}

View File

@ -331,6 +331,7 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
| StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..)
| StatementKind::CopyNonOverlapping(..)
| StatementKind::Assume(..)
| StatementKind::Nop => {}
}
}

View File

@ -105,6 +105,9 @@ impl<'tcx> Visitor<'tcx> for UnsafetyChecker<'_, 'tcx> {
// safe (at least as emitted during MIR construction)
}
// Move to above list once mir construction uses it.
StatementKind::Assume(..) => unreachable!(),
StatementKind::CopyNonOverlapping(..) => unreachable!(),
}
self.super_statement(statement, location);

View File

@ -826,6 +826,7 @@ pub(super) fn filtered_statement_span(statement: &Statement<'_>) -> Option<Span>
// Retain spans from all other statements
StatementKind::FakeRead(box (_, _)) // Not including `ForGuardBinding`
| StatementKind::CopyNonOverlapping(..)
| StatementKind::Assume(..)
| StatementKind::Assign(_)
| StatementKind::SetDiscriminant { .. }
| StatementKind::Deinit(..)

View File

@ -53,6 +53,7 @@ pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, borrowed: &BitS
| StatementKind::StorageDead(_)
| StatementKind::Coverage(_)
| StatementKind::CopyNonOverlapping(_)
| StatementKind::Assume(_)
| StatementKind::Nop => (),
StatementKind::FakeRead(_) | StatementKind::AscribeUserType(_, _) => {

View File

@ -538,6 +538,7 @@ impl<'a> Conflicts<'a> {
| StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..)
| StatementKind::CopyNonOverlapping(..)
| StatementKind::Assume(..)
| StatementKind::Nop => {}
}
}

View File

@ -1453,6 +1453,7 @@ impl<'tcx> Visitor<'tcx> for EnsureGeneratorFieldAssignmentsNeverAlias<'_> {
| StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..)
| StatementKind::CopyNonOverlapping(..)
| StatementKind::Assume(..)
| StatementKind::Nop => {}
}
}

View File

@ -62,6 +62,17 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
drop(args);
terminator.kind = TerminatorKind::Goto { target };
}
sym::assume => {
let target = target.unwrap();
let mut args = args.drain(..);
block.statements.push(Statement {
source_info: terminator.source_info,
kind: StatementKind::Assume(Box::new(args.next().unwrap())),
});
assert_eq!(args.next(), None, "Extra argument for assume intrinsic");
drop(args);
terminator.kind = TerminatorKind::Goto { target };
}
sym::wrapping_add | sym::wrapping_sub | sym::wrapping_mul => {
if let Some(target) = *target {
let lhs;

View File

@ -52,6 +52,7 @@ impl RemoveNoopLandingPads {
| StatementKind::SetDiscriminant { .. }
| StatementKind::Deinit(..)
| StatementKind::CopyNonOverlapping(..)
| StatementKind::Assume(..)
| StatementKind::Retag { .. } => {
return false;
}

View File

@ -250,6 +250,7 @@ fn is_likely_const<'tcx>(mut tracked_place: Place<'tcx>, block: &BasicBlockData<
| StatementKind::Coverage(_)
| StatementKind::StorageDead(_)
| StatementKind::CopyNonOverlapping(_)
| StatementKind::Assume(_)
| StatementKind::Nop => {}
}
}
@ -318,6 +319,7 @@ fn find_determining_place<'tcx>(
| StatementKind::AscribeUserType(_, _)
| StatementKind::Coverage(_)
| StatementKind::CopyNonOverlapping(_)
| StatementKind::Assume(_)
| StatementKind::Nop => {}
// If the discriminant is set, it is always set

View File

@ -500,6 +500,7 @@ impl<'tcx> Visitor<'tcx> for UsedLocals {
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
match statement.kind {
StatementKind::CopyNonOverlapping(..)
| StatementKind::Assume(..)
| StatementKind::Retag(..)
| StatementKind::Coverage(..)
| StatementKind::FakeRead(..)

View File

@ -0,0 +1,26 @@
- // MIR for `assume` before LowerIntrinsics
+ // MIR for `assume` after LowerIntrinsics
fn assume() -> () {
let mut _0: (); // return place in scope 0 at $DIR/lower_intrinsics.rs:+0:17: +0:17
let _1: (); // in scope 0 at $DIR/lower_intrinsics.rs:+2:9: +2:38
scope 1 {
}
bb0: {
StorageLive(_1); // scope 1 at $DIR/lower_intrinsics.rs:+2:9: +2:38
- _1 = std::intrinsics::assume(const true) -> bb1; // scope 1 at $DIR/lower_intrinsics.rs:+2:9: +2:38
- // mir::Constant
- // + span: $DIR/lower_intrinsics.rs:97:9: 97:32
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(bool) {std::intrinsics::assume}, val: Value(<ZST>) }
+ assume(const true); // scope 1 at $DIR/lower_intrinsics.rs:+2:9: +2:38
+ goto -> bb1; // scope 1 at $DIR/lower_intrinsics.rs:+2:9: +2:38
}
bb1: {
StorageDead(_1); // scope 1 at $DIR/lower_intrinsics.rs:+2:38: +2:39
_0 = const (); // scope 1 at $DIR/lower_intrinsics.rs:+1:5: +3:6
return; // scope 0 at $DIR/lower_intrinsics.rs:+4:2: +4:2
}
}

View File

@ -0,0 +1,72 @@
- // MIR for `f_copy_nonoverlapping` before LowerIntrinsics
+ // MIR for `f_copy_nonoverlapping` after LowerIntrinsics
fn f_copy_nonoverlapping() -> () {
let mut _0: (); // return place in scope 0 at $DIR/lower_intrinsics.rs:+0:32: +0:32
let _1: (); // in scope 0 at $DIR/lower_intrinsics.rs:+1:9: +1:12
let _3: (); // in scope 0 at $DIR/lower_intrinsics.rs:+4:9: +4:95
let mut _4: *const i32; // in scope 0 at $DIR/lower_intrinsics.rs:+4:29: +4:59
let mut _5: *const (); // in scope 0 at $DIR/lower_intrinsics.rs:+4:29: +4:45
let mut _6: *const (); // in scope 0 at $DIR/lower_intrinsics.rs:+4:29: +4:45
let _7: &(); // in scope 0 at $DIR/lower_intrinsics.rs:+4:29: +4:33
let mut _8: *mut i32; // in scope 0 at $DIR/lower_intrinsics.rs:+4:61: +4:91
let mut _9: *mut (); // in scope 0 at $DIR/lower_intrinsics.rs:+4:61: +4:79
let mut _10: *mut (); // in scope 0 at $DIR/lower_intrinsics.rs:+4:61: +4:79
let mut _11: &mut (); // in scope 0 at $DIR/lower_intrinsics.rs:+4:61: +4:69
scope 1 {
debug src => _1; // in scope 1 at $DIR/lower_intrinsics.rs:+1:9: +1:12
let mut _2: (); // in scope 1 at $DIR/lower_intrinsics.rs:+2:9: +2:16
scope 2 {
debug dst => _2; // in scope 2 at $DIR/lower_intrinsics.rs:+2:9: +2:16
scope 3 {
}
}
}
bb0: {
StorageLive(_1); // scope 0 at $DIR/lower_intrinsics.rs:+1:9: +1:12
Deinit(_1); // scope 0 at $DIR/lower_intrinsics.rs:+1:15: +1:17
StorageLive(_2); // scope 1 at $DIR/lower_intrinsics.rs:+2:9: +2:16
Deinit(_2); // scope 1 at $DIR/lower_intrinsics.rs:+2:19: +2:21
StorageLive(_3); // scope 3 at $DIR/lower_intrinsics.rs:+4:9: +4:95
StorageLive(_4); // scope 3 at $DIR/lower_intrinsics.rs:+4:29: +4:59
StorageLive(_5); // scope 3 at $DIR/lower_intrinsics.rs:+4:29: +4:45
StorageLive(_6); // scope 3 at $DIR/lower_intrinsics.rs:+4:29: +4:45
StorageLive(_7); // scope 3 at $DIR/lower_intrinsics.rs:+4:29: +4:33
_7 = &_1; // scope 3 at $DIR/lower_intrinsics.rs:+4:29: +4:33
_6 = &raw const (*_7); // scope 3 at $DIR/lower_intrinsics.rs:+4:29: +4:33
_5 = _6; // scope 3 at $DIR/lower_intrinsics.rs:+4:29: +4:45
_4 = move _5 as *const i32 (Misc); // scope 3 at $DIR/lower_intrinsics.rs:+4:29: +4:59
StorageDead(_5); // scope 3 at $DIR/lower_intrinsics.rs:+4:58: +4:59
StorageLive(_8); // scope 3 at $DIR/lower_intrinsics.rs:+4:61: +4:91
StorageLive(_9); // scope 3 at $DIR/lower_intrinsics.rs:+4:61: +4:79
StorageLive(_10); // scope 3 at $DIR/lower_intrinsics.rs:+4:61: +4:79
StorageLive(_11); // scope 3 at $DIR/lower_intrinsics.rs:+4:61: +4:69
_11 = &mut _2; // scope 3 at $DIR/lower_intrinsics.rs:+4:61: +4:69
_10 = &raw mut (*_11); // scope 3 at $DIR/lower_intrinsics.rs:+4:61: +4:69
_9 = _10; // scope 3 at $DIR/lower_intrinsics.rs:+4:61: +4:79
_8 = move _9 as *mut i32 (Misc); // scope 3 at $DIR/lower_intrinsics.rs:+4:61: +4:91
StorageDead(_9); // scope 3 at $DIR/lower_intrinsics.rs:+4:90: +4:91
- _3 = copy_nonoverlapping::<i32>(move _4, move _8, const 0_usize) -> bb1; // scope 3 at $DIR/lower_intrinsics.rs:+4:9: +4:95
- // mir::Constant
- // + span: $DIR/lower_intrinsics.rs:90:9: 90:28
- // + literal: Const { ty: unsafe extern "rust-intrinsic" fn(*const i32, *mut i32, usize) {copy_nonoverlapping::<i32>}, val: Value(<ZST>) }
+ copy_nonoverlapping(src=move _4, dst=move _8, count=const 0_usize); // scope 3 at $DIR/lower_intrinsics.rs:+4:9: +4:95
+ goto -> bb1; // scope 3 at $DIR/lower_intrinsics.rs:+4:9: +4:95
}
bb1: {
StorageDead(_8); // scope 3 at $DIR/lower_intrinsics.rs:+4:94: +4:95
StorageDead(_4); // scope 3 at $DIR/lower_intrinsics.rs:+4:94: +4:95
StorageDead(_11); // scope 3 at $DIR/lower_intrinsics.rs:+4:95: +4:96
StorageDead(_10); // scope 3 at $DIR/lower_intrinsics.rs:+4:95: +4:96
StorageDead(_7); // scope 3 at $DIR/lower_intrinsics.rs:+4:95: +4:96
StorageDead(_6); // scope 3 at $DIR/lower_intrinsics.rs:+4:95: +4:96
StorageDead(_3); // scope 3 at $DIR/lower_intrinsics.rs:+4:95: +4:96
_0 = const (); // scope 3 at $DIR/lower_intrinsics.rs:+3:5: +5:6
StorageDead(_2); // scope 1 at $DIR/lower_intrinsics.rs:+6:1: +6:2
StorageDead(_1); // scope 0 at $DIR/lower_intrinsics.rs:+6:1: +6:2
return; // scope 0 at $DIR/lower_intrinsics.rs:+6:2: +6:2
}
}

View File

@ -1,7 +1,7 @@
// unit-test: LowerIntrinsics
// ignore-wasm32 compiled with panic=abort by default
#![feature(core_intrinsics)]
#![feature(core_intrinsics, intrinsics)]
#![crate_type = "lib"]
// EMIT_MIR lower_intrinsics.wrapping.LowerIntrinsics.diff
@ -51,3 +51,24 @@ pub fn discriminant<T>(t: T) {
core::intrinsics::discriminant_value(&());
core::intrinsics::discriminant_value(&E::B);
}
extern "rust-intrinsic" {
// Cannot use `std::intrinsics::copy_nonoverlapping` as that is a wrapper function
fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize);
}
// EMIT_MIR lower_intrinsics.f_copy_nonoverlapping.LowerIntrinsics.diff
pub fn f_copy_nonoverlapping() {
let src = ();
let mut dst = ();
unsafe {
copy_nonoverlapping(&src as *const _ as *const i32, &mut dst as *mut _ as *mut i32, 0);
}
}
// EMIT_MIR lower_intrinsics.assume.LowerIntrinsics.diff
pub fn assume() {
unsafe {
std::intrinsics::assume(true);
}
}

View File

@ -211,6 +211,9 @@ fn check_statement<'tcx>(
StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => {
check_place(tcx, **place, span, body)
},
StatementKind::Assume(box op) => {
check_operand(tcx, op, span, body)
},
StatementKind::CopyNonOverlapping(box rustc_middle::mir::CopyNonOverlapping { dst, src, count }) => {
check_operand(tcx, dst, span, body)?;