Skip to content

Commit

Permalink
Rollup merge of rust-lang#64890 - wesleywiser:const_prop_rvalue, r=ol…
Browse files Browse the repository at this point in the history
…i-obk

[const-prop] Handle remaining MIR Rvalue cases

r? @oli-obk
  • Loading branch information
Centril authored Oct 1, 2019
2 parents 031d968 + 5804c3b commit 8436080
Show file tree
Hide file tree
Showing 18 changed files with 297 additions and 93 deletions.
164 changes: 78 additions & 86 deletions src/librustc_mir/transform/const_prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use rustc::hir::def::DefKind;
use rustc::hir::def_id::DefId;
use rustc::mir::{
AggregateKind, Constant, Location, Place, PlaceBase, Body, Operand, Rvalue,
Local, NullOp, UnOp, StatementKind, Statement, LocalKind,
Local, UnOp, StatementKind, Statement, LocalKind,
TerminatorKind, Terminator, ClearCrossCrate, SourceInfo, BinOp,
SourceScope, SourceScopeLocalData, LocalDecl, BasicBlock,
};
Expand Down Expand Up @@ -118,7 +118,7 @@ impl<'tcx> MirPass<'tcx> for ConstProp {
struct ConstPropMachine;

impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine {
type MemoryKinds= !;
type MemoryKinds = !;
type PointerTag = ();
type ExtraFnVal = !;

Expand Down Expand Up @@ -459,97 +459,81 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
) -> Option<Const<'tcx>> {
let span = source_info.span;

// if this isn't a supported operation, then return None
match rvalue {
Rvalue::Repeat(..) |
Rvalue::Aggregate(..) |
Rvalue::NullaryOp(NullOp::Box, _) |
Rvalue::Discriminant(..) => return None,

Rvalue::Use(_) |
Rvalue::Len(_) |
Rvalue::Cast(..) |
Rvalue::NullaryOp(..) |
Rvalue::CheckedBinaryOp(..) |
Rvalue::Ref(..) |
Rvalue::UnaryOp(..) |
Rvalue::BinaryOp(..) => { }
}

// perform any special checking for specific Rvalue types
if let Rvalue::UnaryOp(op, arg) = rvalue {
trace!("checking UnaryOp(op = {:?}, arg = {:?})", op, arg);
let overflow_check = self.tcx.sess.overflow_checks();

self.use_ecx(source_info, |this| {
// We check overflow in debug mode already
// so should only check in release mode.
if *op == UnOp::Neg && !overflow_check {
let ty = arg.ty(&this.local_decls, this.tcx);

if ty.is_integral() {
let arg = this.ecx.eval_operand(arg, None)?;
let prim = this.ecx.read_immediate(arg)?;
// Need to do overflow check here: For actual CTFE, MIR
// generation emits code that does this before calling the op.
if prim.to_bits()? == (1 << (prim.layout.size.bits() - 1)) {
throw_panic!(OverflowNeg)
match rvalue {
Rvalue::UnaryOp(UnOp::Neg, arg) => {
trace!("checking UnaryOp(op = Neg, arg = {:?})", arg);
let overflow_check = self.tcx.sess.overflow_checks();

self.use_ecx(source_info, |this| {
// We check overflow in debug mode already
// so should only check in release mode.
if !overflow_check {
let ty = arg.ty(&this.local_decls, this.tcx);

if ty.is_integral() {
let arg = this.ecx.eval_operand(arg, None)?;
let prim = this.ecx.read_immediate(arg)?;
// Need to do overflow check here: For actual CTFE, MIR
// generation emits code that does this before calling the op.
if prim.to_bits()? == (1 << (prim.layout.size.bits() - 1)) {
throw_panic!(OverflowNeg)
}
}
}
}

Ok(())
})?;
} else if let Rvalue::BinaryOp(op, left, right) = rvalue {
trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right);

let r = self.use_ecx(source_info, |this| {
this.ecx.read_immediate(this.ecx.eval_operand(right, None)?)
})?;
if *op == BinOp::Shr || *op == BinOp::Shl {
let left_bits = place_layout.size.bits();
let right_size = r.layout.size;
let r_bits = r.to_scalar().and_then(|r| r.to_bits(right_size));
if r_bits.ok().map_or(false, |b| b >= left_bits as u128) {
let source_scope_local_data = match self.source_scope_local_data {
ClearCrossCrate::Set(ref data) => data,
ClearCrossCrate::Clear => return None,
};
let dir = if *op == BinOp::Shr {
"right"
} else {
"left"
};
let hir_id = source_scope_local_data[source_info.scope].lint_root;
self.tcx.lint_hir(
::rustc::lint::builtin::EXCEEDING_BITSHIFTS,
hir_id,
span,
&format!("attempt to shift {} with overflow", dir));
return None;
}
Ok(())
})?;
}
self.use_ecx(source_info, |this| {
let l = this.ecx.read_immediate(this.ecx.eval_operand(left, None)?)?;
let (_, overflow, _ty) = this.ecx.overflowing_binary_op(*op, l, r)?;

// We check overflow in debug mode already
// so should only check in release mode.
if !this.tcx.sess.overflow_checks() && overflow {
let err = err_panic!(Overflow(*op)).into();
return Err(err);

Rvalue::BinaryOp(op, left, right) => {
trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right);

let r = self.use_ecx(source_info, |this| {
this.ecx.read_immediate(this.ecx.eval_operand(right, None)?)
})?;
if *op == BinOp::Shr || *op == BinOp::Shl {
let left_bits = place_layout.size.bits();
let right_size = r.layout.size;
let r_bits = r.to_scalar().and_then(|r| r.to_bits(right_size));
if r_bits.ok().map_or(false, |b| b >= left_bits as u128) {
let source_scope_local_data = match self.source_scope_local_data {
ClearCrossCrate::Set(ref data) => data,
ClearCrossCrate::Clear => return None,
};
let dir = if *op == BinOp::Shr {
"right"
} else {
"left"
};
let hir_id = source_scope_local_data[source_info.scope].lint_root;
self.tcx.lint_hir(
::rustc::lint::builtin::EXCEEDING_BITSHIFTS,
hir_id,
span,
&format!("attempt to shift {} with overflow", dir));
return None;
}
}
self.use_ecx(source_info, |this| {
let l = this.ecx.read_immediate(this.ecx.eval_operand(left, None)?)?;
let (_, overflow, _ty) = this.ecx.overflowing_binary_op(*op, l, r)?;

// We check overflow in debug mode already
// so should only check in release mode.
if !this.tcx.sess.overflow_checks() && overflow {
let err = err_panic!(Overflow(*op)).into();
return Err(err);
}

Ok(())
})?;
} else if let Rvalue::Ref(_, _, place) = rvalue {
trace!("checking Ref({:?})", place);
// FIXME(wesleywiser) we don't currently handle the case where we try to make a ref
// from a function argument that hasn't been assigned to in this function.
if let Place {
base: PlaceBase::Local(local),
projection: box []
} = place {
Ok(())
})?;
}

Rvalue::Ref(_, _, Place { base: PlaceBase::Local(local), projection: box [] }) => {
trace!("checking Ref({:?})", place);
// FIXME(wesleywiser) we don't currently handle the case where we try to make a ref
// from a function argument that hasn't been assigned to in this function.
let alive =
if let LocalValue::Live(_) = self.ecx.frame().locals[*local].value {
true
Expand All @@ -560,6 +544,14 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
return None;
}
}

Rvalue::Aggregate(_, operands) if operands.len() == 0 => {
// FIXME(wesleywiser): const eval will turn this into a `const Scalar(<ZST>)` that
// `SimplifyLocals` doesn't know it can remove.
return None;
}

_ => { }
}

self.use_ecx(source_info, |this| {
Expand Down
1 change: 1 addition & 0 deletions src/test/compile-fail/consts/const-err3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ fn main() {
//~^ ERROR const_err
let _e = [5u8][1];
//~^ ERROR const_err
//~| ERROR this expression will panic at runtime
black_box(b);
black_box(c);
black_box(d);
Expand Down
25 changes: 25 additions & 0 deletions src/test/mir-opt/const_prop/aggregate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// compile-flags: -O

fn main() {
let x = (0, 1, 2).1 + 0;
}

// END RUST SOURCE
// START rustc.main.ConstProp.before.mir
// bb0: {
// ...
// _3 = (const 0i32, const 1i32, const 2i32);
// _2 = (_3.1: i32);
// _1 = Add(move _2, const 0i32);
// ...
// }
// END rustc.main.ConstProp.before.mir
// START rustc.main.ConstProp.after.mir
// bb0: {
// ...
// _3 = (const 0i32, const 1i32, const 2i32);
// _2 = const 1i32;
// _1 = Add(move _2, const 0i32);
// ...
// }
// END rustc.main.ConstProp.after.mir
53 changes: 53 additions & 0 deletions src/test/mir-opt/const_prop/boxes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// compile-flags: -O

#![feature(box_syntax)]

// Note: this test verifies that we, in fact, do not const prop `box`

fn main() {
let x = *(box 42) + 0;
}

// END RUST SOURCE
// START rustc.main.ConstProp.before.mir
// bb0: {
// ...
// _4 = Box(i32);
// (*_4) = const 42i32;
// _3 = move _4;
// ...
// _2 = (*_3);
// _1 = Add(move _2, const 0i32);
// ...
// drop(_3) -> [return: bb2, unwind: bb1];
// }
// bb1 (cleanup): {
// resume;
// }
// bb2: {
// ...
// _0 = ();
// ...
// }
// END rustc.main.ConstProp.before.mir
// START rustc.main.ConstProp.after.mir
// bb0: {
// ...
// _4 = Box(i32);
// (*_4) = const 42i32;
// _3 = move _4;
// ...
// _2 = (*_3);
// _1 = Add(move _2, const 0i32);
// ...
// drop(_3) -> [return: bb2, unwind: bb1];
// }
// bb1 (cleanup): {
// resume;
// }
// bb2: {
// ...
// _0 = ();
// ...
// }
// END rustc.main.ConstProp.after.mir
53 changes: 53 additions & 0 deletions src/test/mir-opt/const_prop/discriminant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// compile-flags: -O

fn main() {
let x = (if let Some(true) = Some(true) { 42 } else { 10 }) + 0;
}

// END RUST SOURCE
// START rustc.main.ConstProp.before.mir
// bb0: {
// ...
// _3 = std::option::Option::<bool>::Some(const true,);
// _4 = discriminant(_3);
// switchInt(move _4) -> [1isize: bb3, otherwise: bb2];
// }
// bb1: {
// _2 = const 42i32;
// goto -> bb4;
// }
// bb2: {
// _2 = const 10i32;
// goto -> bb4;
// }
// bb3: {
// switchInt(((_3 as Some).0: bool)) -> [false: bb2, otherwise: bb1];
// }
// bb4: {
// _1 = Add(move _2, const 0i32);
// ...
// }
// END rustc.main.ConstProp.before.mir
// START rustc.main.ConstProp.after.mir
// bb0: {
// ...
// _3 = const Scalar(0x01) : std::option::Option<bool>;
// _4 = const 1isize;
// switchInt(const 1isize) -> [1isize: bb3, otherwise: bb2];
// }
// bb1: {
// _2 = const 42i32;
// goto -> bb4;
// }
// bb2: {
// _2 = const 10i32;
// goto -> bb4;
// }
// bb3: {
// switchInt(const true) -> [false: bb2, otherwise: bb1];
// }
// bb4: {
// _1 = Add(move _2, const 0i32);
// ...
// }
// END rustc.main.ConstProp.after.mir
37 changes: 37 additions & 0 deletions src/test/mir-opt/const_prop/repeat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// compile-flags: -O

fn main() {
let x: u32 = [42; 8][2] + 0;
}

// END RUST SOURCE
// START rustc.main.ConstProp.before.mir
// bb0: {
// ...
// _3 = [const 42u32; 8];
// ...
// _4 = const 2usize;
// _5 = const 8usize;
// _6 = Lt(_4, _5);
// assert(move _6, "index out of bounds: the len is move _5 but the index is _4") -> bb1;
// }
// bb1: {
// _2 = _3[_4];
// _1 = Add(move _2, const 0u32);
// ...
// return;
// }
// END rustc.main.ConstProp.before.mir
// START rustc.main.ConstProp.after.mir
// bb0: {
// ...
// _6 = const true;
// assert(const true, "index out of bounds: the len is move _5 but the index is _4") -> bb1;
// }
// bb1: {
// _2 = const 42u32;
// _1 = Add(move _2, const 0u32);
// ...
// return;
// }
// END rustc.main.ConstProp.after.mir
1 change: 1 addition & 0 deletions src/test/run-fail/overflowing-rsh-5.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// compile-flags: -C debug-assertions

#![warn(exceeding_bitshifts)]
#![warn(const_err)]

fn main() {
let _n = 1i64 >> [64][0];
Expand Down
1 change: 1 addition & 0 deletions src/test/run-fail/overflowing-rsh-6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// compile-flags: -C debug-assertions

#![warn(exceeding_bitshifts)]
#![warn(const_err)]
#![feature(const_indexing)]

fn main() {
Expand Down
Loading

0 comments on commit 8436080

Please sign in to comment.