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