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