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