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