]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/transform/const_prop.rs
Rollup merge of #67723 - ldm0:E0477, r=Dylan-DPC
[rust.git] / src / librustc_mir / transform / const_prop.rs
1 //! Propagates constants for early reporting of statically known
2 //! assertion failures
3
4 use std::borrow::Cow;
5 use std::cell::Cell;
6
7 use rustc::hir::def::DefKind;
8 use rustc::hir::def_id::DefId;
9 use rustc::hir::HirId;
10 use rustc::mir::interpret::{InterpResult, PanicInfo, Scalar};
11 use rustc::mir::visit::{
12     MutVisitor, MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor,
13 };
14 use rustc::mir::{
15     read_only, AggregateKind, BasicBlock, BinOp, Body, BodyAndCache, ClearCrossCrate, Constant,
16     Local, LocalDecl, LocalKind, Location, Operand, Place, PlaceBase, ReadOnlyBodyAndCache, Rvalue,
17     SourceInfo, SourceScope, SourceScopeData, Statement, StatementKind, Terminator, TerminatorKind,
18     UnOp, RETURN_PLACE,
19 };
20 use rustc::ty::layout::{
21     HasDataLayout, HasTyCtxt, LayoutError, LayoutOf, Size, TargetDataLayout, TyLayout,
22 };
23 use rustc::ty::subst::InternalSubsts;
24 use rustc::ty::{self, Instance, ParamEnv, Ty, TyCtxt, TypeFoldable};
25 use rustc_data_structures::fx::FxHashMap;
26 use rustc_index::vec::IndexVec;
27 use syntax::ast::Mutability;
28 use syntax_pos::{Span, DUMMY_SP};
29
30 use crate::const_eval::error_to_const_error;
31 use crate::interpret::{
32     self, intern_const_alloc_recursive, AllocId, Allocation, Frame, ImmTy, Immediate, InterpCx,
33     LocalState, LocalValue, Memory, MemoryKind, OpTy, Operand as InterpOperand, PlaceTy, Pointer,
34     ScalarMaybeUndef, StackPopCleanup,
35 };
36 use crate::rustc::ty::subst::Subst;
37 use crate::transform::{MirPass, MirSource};
38
39 /// The maximum number of bytes that we'll allocate space for a return value.
40 const MAX_ALLOC_LIMIT: u64 = 1024;
41
42 pub struct ConstProp;
43
44 impl<'tcx> MirPass<'tcx> for ConstProp {
45     fn run_pass(&self, tcx: TyCtxt<'tcx>, source: MirSource<'tcx>, body: &mut BodyAndCache<'tcx>) {
46         // will be evaluated by miri and produce its errors there
47         if source.promoted.is_some() {
48             return;
49         }
50
51         use rustc::hir::map::blocks::FnLikeNode;
52         let hir_id = tcx
53             .hir()
54             .as_local_hir_id(source.def_id())
55             .expect("Non-local call to local provider is_const_fn");
56
57         let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();
58         let is_assoc_const = match tcx.def_kind(source.def_id()) {
59             Some(DefKind::AssocConst) => true,
60             _ => false,
61         };
62
63         // Only run const prop on functions, methods, closures and associated constants
64         if !is_fn_like && !is_assoc_const {
65             // skip anon_const/statics/consts because they'll be evaluated by miri anyway
66             trace!("ConstProp skipped for {:?}", source.def_id());
67             return;
68         }
69
70         let is_generator = tcx.type_of(source.def_id()).is_generator();
71         // FIXME(welseywiser) const prop doesn't work on generators because of query cycles
72         // computing their layout.
73         if is_generator {
74             trace!("ConstProp skipped for generator {:?}", source.def_id());
75             return;
76         }
77
78         trace!("ConstProp starting for {:?}", source.def_id());
79
80         let dummy_body = &Body::new(
81             body.basic_blocks().clone(),
82             body.source_scopes.clone(),
83             body.local_decls.clone(),
84             Default::default(),
85             body.arg_count,
86             Default::default(),
87             tcx.def_span(source.def_id()),
88             Default::default(),
89             body.generator_kind,
90         );
91
92         // FIXME(oli-obk, eddyb) Optimize locals (or even local paths) to hold
93         // constants, instead of just checking for const-folding succeeding.
94         // That would require an uniform one-def no-mutation analysis
95         // and RPO (or recursing when needing the value of a local).
96         let mut optimization_finder =
97             ConstPropagator::new(read_only!(body), dummy_body, tcx, source);
98         optimization_finder.visit_body(body);
99
100         trace!("ConstProp done for {:?}", source.def_id());
101     }
102 }
103
104 struct ConstPropMachine;
105
106 impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine {
107     type MemoryKinds = !;
108     type PointerTag = ();
109     type ExtraFnVal = !;
110
111     type FrameExtra = ();
112     type MemoryExtra = ();
113     type AllocExtra = ();
114
115     type MemoryMap = FxHashMap<AllocId, (MemoryKind<!>, Allocation)>;
116
117     const STATIC_KIND: Option<!> = None;
118
119     const CHECK_ALIGN: bool = false;
120
121     #[inline(always)]
122     fn enforce_validity(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
123         false
124     }
125
126     fn find_mir_or_eval_fn(
127         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
128         _instance: ty::Instance<'tcx>,
129         _args: &[OpTy<'tcx>],
130         _ret: Option<(PlaceTy<'tcx>, BasicBlock)>,
131         _unwind: Option<BasicBlock>,
132     ) -> InterpResult<'tcx, Option<&'mir Body<'tcx>>> {
133         Ok(None)
134     }
135
136     fn call_extra_fn(
137         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
138         fn_val: !,
139         _args: &[OpTy<'tcx>],
140         _ret: Option<(PlaceTy<'tcx>, BasicBlock)>,
141         _unwind: Option<BasicBlock>,
142     ) -> InterpResult<'tcx> {
143         match fn_val {}
144     }
145
146     fn call_intrinsic(
147         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
148         _span: Span,
149         _instance: ty::Instance<'tcx>,
150         _args: &[OpTy<'tcx>],
151         _ret: Option<(PlaceTy<'tcx>, BasicBlock)>,
152         _unwind: Option<BasicBlock>,
153     ) -> InterpResult<'tcx> {
154         throw_unsup!(ConstPropUnsupported("calling intrinsics isn't supported in ConstProp"));
155     }
156
157     fn assert_panic(
158         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
159         _span: Span,
160         _msg: &rustc::mir::interpret::AssertMessage<'tcx>,
161         _unwind: Option<rustc::mir::BasicBlock>,
162     ) -> InterpResult<'tcx> {
163         bug!("panics terminators are not evaluated in ConstProp");
164     }
165
166     fn ptr_to_int(_mem: &Memory<'mir, 'tcx, Self>, _ptr: Pointer) -> InterpResult<'tcx, u64> {
167         throw_unsup!(ConstPropUnsupported("ptr-to-int casts aren't supported in ConstProp"));
168     }
169
170     fn binary_ptr_op(
171         _ecx: &InterpCx<'mir, 'tcx, Self>,
172         _bin_op: BinOp,
173         _left: ImmTy<'tcx>,
174         _right: ImmTy<'tcx>,
175     ) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)> {
176         // We can't do this because aliasing of memory can differ between const eval and llvm
177         throw_unsup!(ConstPropUnsupported(
178             "pointer arithmetic or comparisons aren't supported \
179             in ConstProp"
180         ));
181     }
182
183     fn find_foreign_static(
184         _tcx: TyCtxt<'tcx>,
185         _def_id: DefId,
186     ) -> InterpResult<'tcx, Cow<'tcx, Allocation<Self::PointerTag>>> {
187         throw_unsup!(ReadForeignStatic)
188     }
189
190     #[inline(always)]
191     fn init_allocation_extra<'b>(
192         _memory_extra: &(),
193         _id: AllocId,
194         alloc: Cow<'b, Allocation>,
195         _kind: Option<MemoryKind<!>>,
196     ) -> (Cow<'b, Allocation<Self::PointerTag>>, Self::PointerTag) {
197         // We do not use a tag so we can just cheaply forward the allocation
198         (alloc, ())
199     }
200
201     #[inline(always)]
202     fn tag_static_base_pointer(_memory_extra: &(), _id: AllocId) -> Self::PointerTag {
203         ()
204     }
205
206     fn box_alloc(
207         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
208         _dest: PlaceTy<'tcx>,
209     ) -> InterpResult<'tcx> {
210         throw_unsup!(ConstPropUnsupported("can't const prop `box` keyword"));
211     }
212
213     fn access_local(
214         _ecx: &InterpCx<'mir, 'tcx, Self>,
215         frame: &Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
216         local: Local,
217     ) -> InterpResult<'tcx, InterpOperand<Self::PointerTag>> {
218         let l = &frame.locals[local];
219
220         if l.value == LocalValue::Uninitialized {
221             throw_unsup!(ConstPropUnsupported("tried to access an uninitialized local"));
222         }
223
224         l.access()
225     }
226
227     fn before_access_static(
228         _memory_extra: &(),
229         allocation: &Allocation<Self::PointerTag, Self::AllocExtra>,
230     ) -> InterpResult<'tcx> {
231         // if the static allocation is mutable or if it has relocations (it may be legal to mutate
232         // the memory behind that in the future), then we can't const prop it
233         if allocation.mutability == Mutability::Mut || allocation.relocations().len() > 0 {
234             throw_unsup!(ConstPropUnsupported("can't eval mutable statics in ConstProp"));
235         }
236
237         Ok(())
238     }
239
240     fn before_terminator(_ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
241         Ok(())
242     }
243
244     #[inline(always)]
245     fn stack_push(_ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
246         Ok(())
247     }
248 }
249
250 type Const<'tcx> = OpTy<'tcx>;
251
252 /// Finds optimization opportunities on the MIR.
253 struct ConstPropagator<'mir, 'tcx> {
254     ecx: InterpCx<'mir, 'tcx, ConstPropMachine>,
255     tcx: TyCtxt<'tcx>,
256     source: MirSource<'tcx>,
257     can_const_prop: IndexVec<Local, ConstPropMode>,
258     param_env: ParamEnv<'tcx>,
259     // FIXME(eddyb) avoid cloning these two fields more than once,
260     // by accessing them through `ecx` instead.
261     source_scopes: IndexVec<SourceScope, SourceScopeData>,
262     local_decls: IndexVec<Local, LocalDecl<'tcx>>,
263     ret: Option<OpTy<'tcx, ()>>,
264     // Because we have `MutVisitor` we can't obtain the `SourceInfo` from a `Location`. So we store
265     // the last known `SourceInfo` here and just keep revisiting it.
266     source_info: Option<SourceInfo>,
267 }
268
269 impl<'mir, 'tcx> LayoutOf for ConstPropagator<'mir, 'tcx> {
270     type Ty = Ty<'tcx>;
271     type TyLayout = Result<TyLayout<'tcx>, LayoutError<'tcx>>;
272
273     fn layout_of(&self, ty: Ty<'tcx>) -> Self::TyLayout {
274         self.tcx.layout_of(self.param_env.and(ty))
275     }
276 }
277
278 impl<'mir, 'tcx> HasDataLayout for ConstPropagator<'mir, 'tcx> {
279     #[inline]
280     fn data_layout(&self) -> &TargetDataLayout {
281         &self.tcx.data_layout
282     }
283 }
284
285 impl<'mir, 'tcx> HasTyCtxt<'tcx> for ConstPropagator<'mir, 'tcx> {
286     #[inline]
287     fn tcx(&self) -> TyCtxt<'tcx> {
288         self.tcx
289     }
290 }
291
292 impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
293     fn new(
294         body: ReadOnlyBodyAndCache<'_, 'tcx>,
295         dummy_body: &'mir Body<'tcx>,
296         tcx: TyCtxt<'tcx>,
297         source: MirSource<'tcx>,
298     ) -> ConstPropagator<'mir, 'tcx> {
299         let def_id = source.def_id();
300         let substs = &InternalSubsts::identity_for_item(tcx, def_id);
301         let mut param_env = tcx.param_env(def_id);
302
303         // If we're evaluating inside a monomorphic function, then use `Reveal::All` because
304         // we want to see the same instances that codegen will see. This allows us to `resolve()`
305         // specializations.
306         if !substs.needs_subst() {
307             param_env = param_env.with_reveal_all();
308         }
309
310         let span = tcx.def_span(def_id);
311         let mut ecx = InterpCx::new(tcx.at(span), param_env, ConstPropMachine, ());
312         let can_const_prop = CanConstProp::check(body);
313
314         let ret = ecx
315             .layout_of(body.return_ty().subst(tcx, substs))
316             .ok()
317             // Don't bother allocating memory for ZST types which have no values
318             // or for large values.
319             .filter(|ret_layout| {
320                 !ret_layout.is_zst() && ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT)
321             })
322             .map(|ret_layout| ecx.allocate(ret_layout, MemoryKind::Stack));
323
324         ecx.push_stack_frame(
325             Instance::new(def_id, substs),
326             span,
327             dummy_body,
328             ret.map(Into::into),
329             StackPopCleanup::None { cleanup: false },
330         )
331         .expect("failed to push initial stack frame");
332
333         ConstPropagator {
334             ecx,
335             tcx,
336             source,
337             param_env,
338             can_const_prop,
339             // FIXME(eddyb) avoid cloning these two fields more than once,
340             // by accessing them through `ecx` instead.
341             source_scopes: body.source_scopes.clone(),
342             //FIXME(wesleywiser) we can't steal this because `Visitor::super_visit_body()` needs it
343             local_decls: body.local_decls.clone(),
344             ret: ret.map(Into::into),
345             source_info: None,
346         }
347     }
348
349     fn get_const(&self, local: Local) -> Option<Const<'tcx>> {
350         if local == RETURN_PLACE {
351             // Try to read the return place as an immediate so that if it is representable as a
352             // scalar, we can handle it as such, but otherwise, just return the value as is.
353             return match self.ret.map(|ret| self.ecx.try_read_immediate(ret)) {
354                 Some(Ok(Ok(imm))) => Some(imm.into()),
355                 _ => self.ret,
356             };
357         }
358
359         self.ecx.access_local(self.ecx.frame(), local, None).ok()
360     }
361
362     fn remove_const(&mut self, local: Local) {
363         self.ecx.frame_mut().locals[local] =
364             LocalState { value: LocalValue::Uninitialized, layout: Cell::new(None) };
365     }
366
367     fn lint_root(&self, source_info: SourceInfo) -> Option<HirId> {
368         match &self.source_scopes[source_info.scope].local_data {
369             ClearCrossCrate::Set(data) => Some(data.lint_root),
370             ClearCrossCrate::Clear => None,
371         }
372     }
373
374     fn use_ecx<F, T>(&mut self, source_info: SourceInfo, f: F) -> Option<T>
375     where
376         F: FnOnce(&mut Self) -> InterpResult<'tcx, T>,
377     {
378         self.ecx.tcx.span = source_info.span;
379         // FIXME(eddyb) move this to the `Panic(_)` error case, so that
380         // `f(self)` is always called, and that the only difference when the
381         // scope's `local_data` is missing, is that the lint isn't emitted.
382         let lint_root = self.lint_root(source_info)?;
383         let r = match f(self) {
384             Ok(val) => Some(val),
385             Err(error) => {
386                 use rustc::mir::interpret::{
387                     InterpError::*, UndefinedBehaviorInfo, UnsupportedOpInfo,
388                 };
389                 match error.kind {
390                     MachineStop(_) => bug!("ConstProp does not stop"),
391
392                     // Some error shouldn't come up because creating them causes
393                     // an allocation, which we should avoid. When that happens,
394                     // dedicated error variants should be introduced instead.
395                     // Only test this in debug builds though to avoid disruptions.
396                     Unsupported(UnsupportedOpInfo::Unsupported(_))
397                     | Unsupported(UnsupportedOpInfo::ValidationFailure(_))
398                     | UndefinedBehavior(UndefinedBehaviorInfo::Ub(_))
399                     | UndefinedBehavior(UndefinedBehaviorInfo::UbExperimental(_))
400                         if cfg!(debug_assertions) =>
401                     {
402                         bug!("const-prop encountered allocating error: {:?}", error.kind);
403                     }
404
405                     Unsupported(_)
406                     | UndefinedBehavior(_)
407                     | InvalidProgram(_)
408                     | ResourceExhaustion(_) => {
409                         // Ignore these errors.
410                     }
411                     Panic(_) => {
412                         let diagnostic = error_to_const_error(&self.ecx, error);
413                         diagnostic.report_as_lint(
414                             self.ecx.tcx,
415                             "this expression will panic at runtime",
416                             lint_root,
417                             None,
418                         );
419                     }
420                 }
421                 None
422             }
423         };
424         self.ecx.tcx.span = DUMMY_SP;
425         r
426     }
427
428     fn eval_constant(
429         &mut self,
430         c: &Constant<'tcx>,
431         source_info: SourceInfo,
432     ) -> Option<Const<'tcx>> {
433         self.ecx.tcx.span = c.span;
434         match self.ecx.eval_const_to_op(c.literal, None) {
435             Ok(op) => Some(op),
436             Err(error) => {
437                 let err = error_to_const_error(&self.ecx, error);
438                 match self.lint_root(source_info) {
439                     Some(lint_root) if c.literal.needs_subst() => {
440                         // Out of backwards compatibility we cannot report hard errors in unused
441                         // generic functions using associated constants of the generic parameters.
442                         err.report_as_lint(
443                             self.ecx.tcx,
444                             "erroneous constant used",
445                             lint_root,
446                             Some(c.span),
447                         );
448                     }
449                     _ => {
450                         err.report_as_error(self.ecx.tcx, "erroneous constant used");
451                     }
452                 }
453                 None
454             }
455         }
456     }
457
458     fn eval_place(&mut self, place: &Place<'tcx>, source_info: SourceInfo) -> Option<Const<'tcx>> {
459         trace!("eval_place(place={:?})", place);
460         self.use_ecx(source_info, |this| this.ecx.eval_place_to_op(place, None))
461     }
462
463     fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<Const<'tcx>> {
464         match *op {
465             Operand::Constant(ref c) => self.eval_constant(c, source_info),
466             Operand::Move(ref place) | Operand::Copy(ref place) => {
467                 self.eval_place(place, source_info)
468             }
469         }
470     }
471
472     fn const_prop(
473         &mut self,
474         rvalue: &Rvalue<'tcx>,
475         place_layout: TyLayout<'tcx>,
476         source_info: SourceInfo,
477         place: &Place<'tcx>,
478     ) -> Option<()> {
479         let span = source_info.span;
480
481         // #66397: Don't try to eval into large places as that can cause an OOM
482         if place_layout.size >= Size::from_bytes(MAX_ALLOC_LIMIT) {
483             return None;
484         }
485
486         let overflow_check = self.tcx.sess.overflow_checks();
487
488         // Perform any special handling for specific Rvalue types.
489         // Generally, checks here fall into one of two categories:
490         //   1. Additional checking to provide useful lints to the user
491         //        - In this case, we will do some validation and then fall through to the
492         //          end of the function which evals the assignment.
493         //   2. Working around bugs in other parts of the compiler
494         //        - In this case, we'll return `None` from this function to stop evaluation.
495         match rvalue {
496             // Additional checking: if overflow checks are disabled (which is usually the case in
497             // release mode), then we need to do additional checking here to give lints to the user
498             // if an overflow would occur.
499             Rvalue::UnaryOp(UnOp::Neg, arg) if !overflow_check => {
500                 trace!("checking UnaryOp(op = Neg, arg = {:?})", arg);
501
502                 self.use_ecx(source_info, |this| {
503                     let ty = arg.ty(&this.local_decls, this.tcx);
504
505                     if ty.is_integral() {
506                         let arg = this.ecx.eval_operand(arg, None)?;
507                         let prim = this.ecx.read_immediate(arg)?;
508                         // Need to do overflow check here: For actual CTFE, MIR
509                         // generation emits code that does this before calling the op.
510                         if prim.to_bits()? == (1 << (prim.layout.size.bits() - 1)) {
511                             throw_panic!(OverflowNeg)
512                         }
513                     }
514
515                     Ok(())
516                 })?;
517             }
518
519             // Additional checking: check for overflows on integer binary operations and report
520             // them to the user as lints.
521             Rvalue::BinaryOp(op, left, right) => {
522                 trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right);
523
524                 let r = self.use_ecx(source_info, |this| {
525                     this.ecx.read_immediate(this.ecx.eval_operand(right, None)?)
526                 })?;
527                 if *op == BinOp::Shr || *op == BinOp::Shl {
528                     let left_bits = place_layout.size.bits();
529                     let right_size = r.layout.size;
530                     let r_bits = r.to_scalar().and_then(|r| r.to_bits(right_size));
531                     if r_bits.map_or(false, |b| b >= left_bits as u128) {
532                         let lint_root = self.lint_root(source_info)?;
533                         let dir = if *op == BinOp::Shr { "right" } else { "left" };
534                         self.tcx.lint_hir(
535                             ::rustc::lint::builtin::EXCEEDING_BITSHIFTS,
536                             lint_root,
537                             span,
538                             &format!("attempt to shift {} with overflow", dir),
539                         );
540                         return None;
541                     }
542                 }
543
544                 // If overflow checking is enabled (like in debug mode by default),
545                 // then we'll already catch overflow when we evaluate the `Assert` statement
546                 // in MIR. However, if overflow checking is disabled, then there won't be any
547                 // `Assert` statement and so we have to do additional checking here.
548                 if !overflow_check {
549                     self.use_ecx(source_info, |this| {
550                         let l = this.ecx.read_immediate(this.ecx.eval_operand(left, None)?)?;
551                         let (_, overflow, _ty) = this.ecx.overflowing_binary_op(*op, l, r)?;
552
553                         if overflow {
554                             let err = err_panic!(Overflow(*op)).into();
555                             return Err(err);
556                         }
557
558                         Ok(())
559                     })?;
560                 }
561             }
562
563             // Work around: avoid ICE in miri. FIXME(wesleywiser)
564             // The Miri engine ICEs when taking a reference to an uninitialized unsized
565             // local. There's nothing it can do here: taking a reference needs an allocation
566             // which needs to know the size. Normally that's okay as during execution
567             // (e.g. for CTFE) it can never happen. But here in const_prop
568             // unknown data is uninitialized, so if e.g. a function argument is unsized
569             // and has a reference taken, we get an ICE.
570             Rvalue::Ref(_, _, place_ref) => {
571                 trace!("checking Ref({:?})", place_ref);
572
573                 if let Some(local) = place_ref.as_local() {
574                     let alive = if let LocalValue::Live(_) = self.ecx.frame().locals[local].value {
575                         true
576                     } else {
577                         false
578                     };
579
580                     if !alive {
581                         trace!("skipping Ref({:?}) to uninitialized local", place);
582                         return None;
583                     }
584                 }
585             }
586
587             _ => {}
588         }
589
590         self.use_ecx(source_info, |this| {
591             trace!("calling eval_rvalue_into_place(rvalue = {:?}, place = {:?})", rvalue, place);
592             this.ecx.eval_rvalue_into_place(rvalue, place)?;
593             Ok(())
594         })
595     }
596
597     fn operand_from_scalar(&self, scalar: Scalar, ty: Ty<'tcx>, span: Span) -> Operand<'tcx> {
598         Operand::Constant(Box::new(Constant {
599             span,
600             user_ty: None,
601             literal: self.tcx.mk_const(*ty::Const::from_scalar(self.tcx, scalar, ty)),
602         }))
603     }
604
605     fn replace_with_const(
606         &mut self,
607         rval: &mut Rvalue<'tcx>,
608         value: Const<'tcx>,
609         source_info: SourceInfo,
610     ) {
611         trace!("attepting to replace {:?} with {:?}", rval, value);
612         if let Err(e) = self.ecx.validate_operand(
613             value,
614             vec![],
615             // FIXME: is ref tracking too expensive?
616             Some(&mut interpret::RefTracking::empty()),
617         ) {
618             trace!("validation error, attempt failed: {:?}", e);
619             return;
620         }
621
622         // FIXME> figure out what tho do when try_read_immediate fails
623         let imm = self.use_ecx(source_info, |this| this.ecx.try_read_immediate(value));
624
625         if let Some(Ok(imm)) = imm {
626             match *imm {
627                 interpret::Immediate::Scalar(ScalarMaybeUndef::Scalar(scalar)) => {
628                     *rval = Rvalue::Use(self.operand_from_scalar(
629                         scalar,
630                         value.layout.ty,
631                         source_info.span,
632                     ));
633                 }
634                 Immediate::ScalarPair(
635                     ScalarMaybeUndef::Scalar(one),
636                     ScalarMaybeUndef::Scalar(two),
637                 ) => {
638                     // Found a value represented as a pair. For now only do cont-prop if type of
639                     // Rvalue is also a pair with two scalars. The more general case is more
640                     // complicated to implement so we'll do it later.
641                     let ty = &value.layout.ty.kind;
642                     // Only do it for tuples
643                     if let ty::Tuple(substs) = ty {
644                         // Only do it if tuple is also a pair with two scalars
645                         if substs.len() == 2 {
646                             let opt_ty1_ty2 = self.use_ecx(source_info, |this| {
647                                 let ty1 = substs[0].expect_ty();
648                                 let ty2 = substs[1].expect_ty();
649                                 let ty_is_scalar = |ty| {
650                                     this.ecx.layout_of(ty).ok().map(|ty| ty.details.abi.is_scalar())
651                                         == Some(true)
652                                 };
653                                 if ty_is_scalar(ty1) && ty_is_scalar(ty2) {
654                                     Ok(Some((ty1, ty2)))
655                                 } else {
656                                     Ok(None)
657                                 }
658                             });
659
660                             if let Some(Some((ty1, ty2))) = opt_ty1_ty2 {
661                                 *rval = Rvalue::Aggregate(
662                                     Box::new(AggregateKind::Tuple),
663                                     vec![
664                                         self.operand_from_scalar(one, ty1, source_info.span),
665                                         self.operand_from_scalar(two, ty2, source_info.span),
666                                     ],
667                                 );
668                             }
669                         }
670                     }
671                 }
672                 _ => {}
673             }
674         }
675     }
676
677     fn should_const_prop(&mut self, op: OpTy<'tcx>) -> bool {
678         let mir_opt_level = self.tcx.sess.opts.debugging_opts.mir_opt_level;
679
680         if mir_opt_level == 0 {
681             return false;
682         }
683
684         match *op {
685             interpret::Operand::Immediate(Immediate::Scalar(ScalarMaybeUndef::Scalar(s))) => {
686                 s.is_bits()
687             }
688             interpret::Operand::Immediate(Immediate::ScalarPair(
689                 ScalarMaybeUndef::Scalar(l),
690                 ScalarMaybeUndef::Scalar(r),
691             )) => l.is_bits() && r.is_bits(),
692             interpret::Operand::Indirect(_) if mir_opt_level >= 2 => {
693                 intern_const_alloc_recursive(&mut self.ecx, None, op.assert_mem_place())
694                     .expect("failed to intern alloc");
695                 true
696             }
697             _ => false,
698         }
699     }
700 }
701
702 /// The mode that `ConstProp` is allowed to run in for a given `Local`.
703 #[derive(Clone, Copy, Debug, PartialEq)]
704 enum ConstPropMode {
705     /// The `Local` can be propagated into and reads of this `Local` can also be propagated.
706     FullConstProp,
707     /// The `Local` can be propagated into but reads cannot be propagated.
708     OnlyPropagateInto,
709     /// No propagation is allowed at all.
710     NoPropagation,
711 }
712
713 struct CanConstProp {
714     can_const_prop: IndexVec<Local, ConstPropMode>,
715     // false at the beginning, once set, there are not allowed to be any more assignments
716     found_assignment: IndexVec<Local, bool>,
717 }
718
719 impl CanConstProp {
720     /// returns true if `local` can be propagated
721     fn check(body: ReadOnlyBodyAndCache<'_, '_>) -> IndexVec<Local, ConstPropMode> {
722         let mut cpv = CanConstProp {
723             can_const_prop: IndexVec::from_elem(ConstPropMode::FullConstProp, &body.local_decls),
724             found_assignment: IndexVec::from_elem(false, &body.local_decls),
725         };
726         for (local, val) in cpv.can_const_prop.iter_enumerated_mut() {
727             // cannot use args at all
728             // cannot use locals because if x < y { y - x } else { x - y } would
729             //        lint for x != y
730             // FIXME(oli-obk): lint variables until they are used in a condition
731             // FIXME(oli-obk): lint if return value is constant
732             let local_kind = body.local_kind(local);
733
734             if local_kind == LocalKind::Arg || local_kind == LocalKind::Var {
735                 *val = ConstPropMode::OnlyPropagateInto;
736                 trace!("local {:?} can't be const propagated because it's not a temporary", local);
737             }
738         }
739         cpv.visit_body(body);
740         cpv.can_const_prop
741     }
742 }
743
744 impl<'tcx> Visitor<'tcx> for CanConstProp {
745     fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) {
746         use rustc::mir::visit::PlaceContext::*;
747         match context {
748             // Constants must have at most one write
749             // FIXME(oli-obk): we could be more powerful here, if the multiple writes
750             // only occur in independent execution paths
751             MutatingUse(MutatingUseContext::Store) => {
752                 if self.found_assignment[local] {
753                     trace!("local {:?} can't be propagated because of multiple assignments", local);
754                     self.can_const_prop[local] = ConstPropMode::NoPropagation;
755                 } else {
756                     self.found_assignment[local] = true
757                 }
758             }
759             // Reading constants is allowed an arbitrary number of times
760             NonMutatingUse(NonMutatingUseContext::Copy)
761             | NonMutatingUse(NonMutatingUseContext::Move)
762             | NonMutatingUse(NonMutatingUseContext::Inspect)
763             | NonMutatingUse(NonMutatingUseContext::Projection)
764             | MutatingUse(MutatingUseContext::Projection)
765             | NonUse(_) => {}
766             _ => {
767                 trace!("local {:?} can't be propagaged because it's used: {:?}", local, context);
768                 self.can_const_prop[local] = ConstPropMode::NoPropagation;
769             }
770         }
771     }
772 }
773
774 impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> {
775     fn tcx(&self) -> TyCtxt<'tcx> {
776         self.tcx
777     }
778
779     fn visit_constant(&mut self, constant: &mut Constant<'tcx>, location: Location) {
780         trace!("visit_constant: {:?}", constant);
781         self.super_constant(constant, location);
782         self.eval_constant(constant, self.source_info.unwrap());
783     }
784
785     fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
786         trace!("visit_statement: {:?}", statement);
787         let source_info = statement.source_info;
788         self.source_info = Some(source_info);
789         if let StatementKind::Assign(box (ref place, ref mut rval)) = statement.kind {
790             let place_ty: Ty<'tcx> = place.ty(&self.local_decls, self.tcx).ty;
791             if let Ok(place_layout) = self.tcx.layout_of(self.param_env.and(place_ty)) {
792                 if let Some(local) = place.as_local() {
793                     let can_const_prop = self.can_const_prop[local];
794                     if let Some(()) = self.const_prop(rval, place_layout, source_info, place) {
795                         if can_const_prop == ConstPropMode::FullConstProp
796                             || can_const_prop == ConstPropMode::OnlyPropagateInto
797                         {
798                             if let Some(value) = self.get_const(local) {
799                                 if self.should_const_prop(value) {
800                                     trace!("replacing {:?} with {:?}", rval, value);
801                                     self.replace_with_const(rval, value, statement.source_info);
802
803                                     if can_const_prop == ConstPropMode::FullConstProp {
804                                         trace!("propagated into {:?}", local);
805                                     }
806                                 }
807                             }
808                         }
809                     }
810                     if self.can_const_prop[local] != ConstPropMode::FullConstProp {
811                         trace!("can't propagate into {:?}", local);
812                         if local != RETURN_PLACE {
813                             self.remove_const(local);
814                         }
815                     }
816                 }
817             }
818         } else {
819             match statement.kind {
820                 StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
821                     let frame = self.ecx.frame_mut();
822                     frame.locals[local].value =
823                         if let StatementKind::StorageLive(_) = statement.kind {
824                             LocalValue::Uninitialized
825                         } else {
826                             LocalValue::Dead
827                         };
828                 }
829                 _ => {}
830             }
831         }
832
833         self.super_statement(statement, location);
834     }
835
836     fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Location) {
837         let source_info = terminator.source_info;
838         self.source_info = Some(source_info);
839         self.super_terminator(terminator, location);
840         match &mut terminator.kind {
841             TerminatorKind::Assert { expected, ref msg, ref mut cond, .. } => {
842                 if let Some(value) = self.eval_operand(&cond, source_info) {
843                     trace!("assertion on {:?} should be {:?}", value, expected);
844                     let expected = ScalarMaybeUndef::from(Scalar::from_bool(*expected));
845                     let value_const = self.ecx.read_scalar(value).unwrap();
846                     if expected != value_const {
847                         // poison all places this operand references so that further code
848                         // doesn't use the invalid value
849                         match cond {
850                             Operand::Move(ref place) | Operand::Copy(ref place) => {
851                                 if let PlaceBase::Local(local) = place.base {
852                                     self.remove_const(local);
853                                 }
854                             }
855                             Operand::Constant(_) => {}
856                         }
857                         let span = terminator.source_info.span;
858                         let hir_id = self
859                             .tcx
860                             .hir()
861                             .as_local_hir_id(self.source.def_id())
862                             .expect("some part of a failing const eval must be local");
863                         let msg = match msg {
864                             PanicInfo::Overflow(_)
865                             | PanicInfo::OverflowNeg
866                             | PanicInfo::DivisionByZero
867                             | PanicInfo::RemainderByZero => msg.description().to_owned(),
868                             PanicInfo::BoundsCheck { ref len, ref index } => {
869                                 let len =
870                                     self.eval_operand(len, source_info).expect("len must be const");
871                                 let len = match self.ecx.read_scalar(len) {
872                                     Ok(ScalarMaybeUndef::Scalar(Scalar::Raw { data, .. })) => data,
873                                     other => bug!("const len not primitive: {:?}", other),
874                                 };
875                                 let index = self
876                                     .eval_operand(index, source_info)
877                                     .expect("index must be const");
878                                 let index = match self.ecx.read_scalar(index) {
879                                     Ok(ScalarMaybeUndef::Scalar(Scalar::Raw { data, .. })) => data,
880                                     other => bug!("const index not primitive: {:?}", other),
881                                 };
882                                 format!(
883                                     "index out of bounds: \
884                                     the len is {} but the index is {}",
885                                     len, index,
886                                 )
887                             }
888                             // Need proper const propagator for these
889                             _ => return,
890                         };
891                         self.tcx.lint_hir(::rustc::lint::builtin::CONST_ERR, hir_id, span, &msg);
892                     } else {
893                         if self.should_const_prop(value) {
894                             if let ScalarMaybeUndef::Scalar(scalar) = value_const {
895                                 *cond = self.operand_from_scalar(
896                                     scalar,
897                                     self.tcx.types.bool,
898                                     source_info.span,
899                                 );
900                             }
901                         }
902                     }
903                 }
904             }
905             TerminatorKind::SwitchInt { ref mut discr, switch_ty, .. } => {
906                 if let Some(value) = self.eval_operand(&discr, source_info) {
907                     if self.should_const_prop(value) {
908                         if let ScalarMaybeUndef::Scalar(scalar) =
909                             self.ecx.read_scalar(value).unwrap()
910                         {
911                             *discr = self.operand_from_scalar(scalar, switch_ty, source_info.span);
912                         }
913                     }
914                 }
915             }
916             //none of these have Operands to const-propagate
917             TerminatorKind::Goto { .. }
918             | TerminatorKind::Resume
919             | TerminatorKind::Abort
920             | TerminatorKind::Return
921             | TerminatorKind::Unreachable
922             | TerminatorKind::Drop { .. }
923             | TerminatorKind::DropAndReplace { .. }
924             | TerminatorKind::Yield { .. }
925             | TerminatorKind::GeneratorDrop
926             | TerminatorKind::FalseEdges { .. }
927             | TerminatorKind::FalseUnwind { .. } => {}
928             //FIXME(wesleywiser) Call does have Operands that could be const-propagated
929             TerminatorKind::Call { .. } => {}
930         }
931     }
932 }