]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir_transform/src/const_prop.rs
Spellchecking compiler comments
[rust.git] / compiler / rustc_mir_transform / src / const_prop.rs
1 //! Propagates constants for early reporting of statically known
2 //! assertion failures
3
4 use std::cell::Cell;
5
6 use rustc_ast::Mutability;
7 use rustc_data_structures::fx::FxHashSet;
8 use rustc_hir::def::DefKind;
9 use rustc_index::bit_set::BitSet;
10 use rustc_index::vec::IndexVec;
11 use rustc_middle::mir::visit::{
12     MutVisitor, MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor,
13 };
14 use rustc_middle::mir::{
15     BasicBlock, BinOp, Body, Constant, ConstantKind, Local, LocalDecl, LocalKind, Location,
16     Operand, Place, Rvalue, SourceInfo, Statement, StatementKind, Terminator, TerminatorKind, UnOp,
17     RETURN_PLACE,
18 };
19 use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
20 use rustc_middle::ty::subst::{InternalSubsts, Subst};
21 use rustc_middle::ty::{self, ConstKind, Instance, ParamEnv, Ty, TyCtxt, TypeFoldable};
22 use rustc_span::{def_id::DefId, Span};
23 use rustc_target::abi::{HasDataLayout, Size, TargetDataLayout};
24 use rustc_target::spec::abi::Abi;
25 use rustc_trait_selection::traits;
26
27 use crate::MirPass;
28 use rustc_const_eval::interpret::{
29     self, compile_time_machine, AllocId, ConstAllocation, ConstValue, CtfeValidationMode, Frame,
30     ImmTy, Immediate, InterpCx, InterpResult, LocalState, LocalValue, MemPlace, MemoryKind, OpTy,
31     Operand as InterpOperand, PlaceTy, Scalar, ScalarMaybeUninit, StackPopCleanup, StackPopUnwind,
32 };
33
34 /// The maximum number of bytes that we'll allocate space for a local or the return value.
35 /// Needed for #66397, because otherwise we eval into large places and that can cause OOM or just
36 /// Severely regress performance.
37 const MAX_ALLOC_LIMIT: u64 = 1024;
38
39 /// Macro for machine-specific `InterpError` without allocation.
40 /// (These will never be shown to the user, but they help diagnose ICEs.)
41 macro_rules! throw_machine_stop_str {
42     ($($tt:tt)*) => {{
43         // We make a new local type for it. The type itself does not carry any information,
44         // but its vtable (for the `MachineStopType` trait) does.
45         struct Zst;
46         // Printing this type shows the desired string.
47         impl std::fmt::Display for Zst {
48             fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49                 write!(f, $($tt)*)
50             }
51         }
52         impl rustc_middle::mir::interpret::MachineStopType for Zst {}
53         throw_machine_stop!(Zst)
54     }};
55 }
56
57 pub struct ConstProp;
58
59 impl<'tcx> MirPass<'tcx> for ConstProp {
60     fn is_enabled(&self, _sess: &rustc_session::Session) -> bool {
61         // FIXME(#70073): Unlike the other passes in "optimizations", this one emits errors, so it
62         // runs even when MIR optimizations are disabled. We should separate the lint out from the
63         // transform and move the lint as early in the pipeline as possible.
64         true
65     }
66
67     fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
68         // will be evaluated by miri and produce its errors there
69         if body.source.promoted.is_some() {
70             return;
71         }
72
73         let def_id = body.source.def_id().expect_local();
74         let is_fn_like = tcx.hir().get_by_def_id(def_id).fn_kind().is_some();
75         let is_assoc_const = tcx.def_kind(def_id) == DefKind::AssocConst;
76
77         // Only run const prop on functions, methods, closures and associated constants
78         if !is_fn_like && !is_assoc_const {
79             // skip anon_const/statics/consts because they'll be evaluated by miri anyway
80             trace!("ConstProp skipped for {:?}", def_id);
81             return;
82         }
83
84         let is_generator = tcx.type_of(def_id.to_def_id()).is_generator();
85         // FIXME(welseywiser) const prop doesn't work on generators because of query cycles
86         // computing their layout.
87         if is_generator {
88             trace!("ConstProp skipped for generator {:?}", def_id);
89             return;
90         }
91
92         // Check if it's even possible to satisfy the 'where' clauses
93         // for this item.
94         // This branch will never be taken for any normal function.
95         // However, it's possible to `#!feature(trivial_bounds)]` to write
96         // a function with impossible to satisfy clauses, e.g.:
97         // `fn foo() where String: Copy {}`
98         //
99         // We don't usually need to worry about this kind of case,
100         // since we would get a compilation error if the user tried
101         // to call it. However, since we can do const propagation
102         // even without any calls to the function, we need to make
103         // sure that it even makes sense to try to evaluate the body.
104         // If there are unsatisfiable where clauses, then all bets are
105         // off, and we just give up.
106         //
107         // We manually filter the predicates, skipping anything that's not
108         // "global". We are in a potentially generic context
109         // (e.g. we are evaluating a function without substituting generic
110         // parameters, so this filtering serves two purposes:
111         //
112         // 1. We skip evaluating any predicates that we would
113         // never be able prove are unsatisfiable (e.g. `<T as Foo>`
114         // 2. We avoid trying to normalize predicates involving generic
115         // parameters (e.g. `<T as Foo>::MyItem`). This can confuse
116         // the normalization code (leading to cycle errors), since
117         // it's usually never invoked in this way.
118         let predicates = tcx
119             .predicates_of(def_id.to_def_id())
120             .predicates
121             .iter()
122             .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
123         if traits::impossible_predicates(
124             tcx,
125             traits::elaborate_predicates(tcx, predicates).map(|o| o.predicate).collect(),
126         ) {
127             trace!("ConstProp skipped for {:?}: found unsatisfiable predicates", def_id);
128             return;
129         }
130
131         trace!("ConstProp starting for {:?}", def_id);
132
133         let dummy_body = &Body::new(
134             body.source,
135             body.basic_blocks().clone(),
136             body.source_scopes.clone(),
137             body.local_decls.clone(),
138             Default::default(),
139             body.arg_count,
140             Default::default(),
141             body.span,
142             body.generator_kind(),
143             body.tainted_by_errors,
144         );
145
146         // FIXME(oli-obk, eddyb) Optimize locals (or even local paths) to hold
147         // constants, instead of just checking for const-folding succeeding.
148         // That would require a uniform one-def no-mutation analysis
149         // and RPO (or recursing when needing the value of a local).
150         let mut optimization_finder = ConstPropagator::new(body, dummy_body, tcx);
151         optimization_finder.visit_body(body);
152
153         trace!("ConstProp done for {:?}", def_id);
154     }
155 }
156
157 struct ConstPropMachine<'mir, 'tcx> {
158     /// The virtual call stack.
159     stack: Vec<Frame<'mir, 'tcx>>,
160     /// `OnlyInsideOwnBlock` locals that were written in the current block get erased at the end.
161     written_only_inside_own_block_locals: FxHashSet<Local>,
162     /// Locals that need to be cleared after every block terminates.
163     only_propagate_inside_block_locals: BitSet<Local>,
164     can_const_prop: IndexVec<Local, ConstPropMode>,
165 }
166
167 impl ConstPropMachine<'_, '_> {
168     fn new(
169         only_propagate_inside_block_locals: BitSet<Local>,
170         can_const_prop: IndexVec<Local, ConstPropMode>,
171     ) -> Self {
172         Self {
173             stack: Vec::new(),
174             written_only_inside_own_block_locals: Default::default(),
175             only_propagate_inside_block_locals,
176             can_const_prop,
177         }
178     }
179 }
180
181 impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx> {
182     compile_time_machine!(<'mir, 'tcx>);
183     const PANIC_ON_ALLOC_FAIL: bool = true; // all allocations are small (see `MAX_ALLOC_LIMIT`)
184
185     type MemoryKind = !;
186
187     type MemoryExtra = ();
188
189     fn load_mir(
190         _ecx: &InterpCx<'mir, 'tcx, Self>,
191         _instance: ty::InstanceDef<'tcx>,
192     ) -> InterpResult<'tcx, &'tcx Body<'tcx>> {
193         throw_machine_stop_str!("calling functions isn't supported in ConstProp")
194     }
195
196     fn find_mir_or_eval_fn(
197         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
198         _instance: ty::Instance<'tcx>,
199         _abi: Abi,
200         _args: &[OpTy<'tcx>],
201         _ret: Option<(&PlaceTy<'tcx>, BasicBlock)>,
202         _unwind: StackPopUnwind,
203     ) -> InterpResult<'tcx, Option<(&'mir Body<'tcx>, ty::Instance<'tcx>)>> {
204         Ok(None)
205     }
206
207     fn call_intrinsic(
208         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
209         _instance: ty::Instance<'tcx>,
210         _args: &[OpTy<'tcx>],
211         _ret: Option<(&PlaceTy<'tcx>, BasicBlock)>,
212         _unwind: StackPopUnwind,
213     ) -> InterpResult<'tcx> {
214         throw_machine_stop_str!("calling intrinsics isn't supported in ConstProp")
215     }
216
217     fn assert_panic(
218         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
219         _msg: &rustc_middle::mir::AssertMessage<'tcx>,
220         _unwind: Option<rustc_middle::mir::BasicBlock>,
221     ) -> InterpResult<'tcx> {
222         bug!("panics terminators are not evaluated in ConstProp")
223     }
224
225     fn binary_ptr_op(
226         _ecx: &InterpCx<'mir, 'tcx, Self>,
227         _bin_op: BinOp,
228         _left: &ImmTy<'tcx>,
229         _right: &ImmTy<'tcx>,
230     ) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)> {
231         // We can't do this because aliasing of memory can differ between const eval and llvm
232         throw_machine_stop_str!("pointer arithmetic or comparisons aren't supported in ConstProp")
233     }
234
235     fn access_local(
236         _ecx: &InterpCx<'mir, 'tcx, Self>,
237         frame: &Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
238         local: Local,
239     ) -> InterpResult<'tcx, InterpOperand<Self::PointerTag>> {
240         let l = &frame.locals[local];
241
242         if l.value == LocalValue::Unallocated {
243             throw_machine_stop_str!("tried to access an unallocated local")
244         }
245
246         l.access()
247     }
248
249     fn access_local_mut<'a>(
250         ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
251         frame: usize,
252         local: Local,
253     ) -> InterpResult<'tcx, Result<&'a mut LocalValue<Self::PointerTag>, MemPlace<Self::PointerTag>>>
254     {
255         if ecx.machine.can_const_prop[local] == ConstPropMode::NoPropagation {
256             throw_machine_stop_str!("tried to write to a local that is marked as not propagatable")
257         }
258         if frame == 0 && ecx.machine.only_propagate_inside_block_locals.contains(local) {
259             trace!(
260                 "mutating local {:?} which is restricted to its block. \
261                 Will remove it from const-prop after block is finished.",
262                 local
263             );
264             ecx.machine.written_only_inside_own_block_locals.insert(local);
265         }
266         ecx.machine.stack[frame].locals[local].access_mut()
267     }
268
269     fn before_access_global(
270         _memory_extra: &(),
271         _alloc_id: AllocId,
272         alloc: ConstAllocation<'tcx, Self::PointerTag, Self::AllocExtra>,
273         _static_def_id: Option<DefId>,
274         is_write: bool,
275     ) -> InterpResult<'tcx> {
276         if is_write {
277             throw_machine_stop_str!("can't write to global");
278         }
279         // If the static allocation is mutable, then we can't const prop it as its content
280         // might be different at runtime.
281         if alloc.inner().mutability == Mutability::Mut {
282             throw_machine_stop_str!("can't access mutable globals in ConstProp");
283         }
284
285         Ok(())
286     }
287
288     #[inline(always)]
289     fn init_frame_extra(
290         _ecx: &mut InterpCx<'mir, 'tcx, Self>,
291         frame: Frame<'mir, 'tcx>,
292     ) -> InterpResult<'tcx, Frame<'mir, 'tcx>> {
293         Ok(frame)
294     }
295
296     #[inline(always)]
297     fn stack<'a>(
298         ecx: &'a InterpCx<'mir, 'tcx, Self>,
299     ) -> &'a [Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>] {
300         &ecx.machine.stack
301     }
302
303     #[inline(always)]
304     fn stack_mut<'a>(
305         ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
306     ) -> &'a mut Vec<Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>> {
307         &mut ecx.machine.stack
308     }
309 }
310
311 /// Finds optimization opportunities on the MIR.
312 struct ConstPropagator<'mir, 'tcx> {
313     ecx: InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>,
314     tcx: TyCtxt<'tcx>,
315     param_env: ParamEnv<'tcx>,
316     // FIXME(eddyb) avoid cloning this field more than once,
317     // by accessing it through `ecx` instead.
318     local_decls: IndexVec<Local, LocalDecl<'tcx>>,
319     // Because we have `MutVisitor` we can't obtain the `SourceInfo` from a `Location`. So we store
320     // the last known `SourceInfo` here and just keep revisiting it.
321     source_info: Option<SourceInfo>,
322 }
323
324 impl<'tcx> LayoutOfHelpers<'tcx> for ConstPropagator<'_, 'tcx> {
325     type LayoutOfResult = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>;
326
327     #[inline]
328     fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> LayoutError<'tcx> {
329         err
330     }
331 }
332
333 impl HasDataLayout for ConstPropagator<'_, '_> {
334     #[inline]
335     fn data_layout(&self) -> &TargetDataLayout {
336         &self.tcx.data_layout
337     }
338 }
339
340 impl<'tcx> ty::layout::HasTyCtxt<'tcx> for ConstPropagator<'_, 'tcx> {
341     #[inline]
342     fn tcx(&self) -> TyCtxt<'tcx> {
343         self.tcx
344     }
345 }
346
347 impl<'tcx> ty::layout::HasParamEnv<'tcx> for ConstPropagator<'_, 'tcx> {
348     #[inline]
349     fn param_env(&self) -> ty::ParamEnv<'tcx> {
350         self.param_env
351     }
352 }
353
354 impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
355     fn new(
356         body: &Body<'tcx>,
357         dummy_body: &'mir Body<'tcx>,
358         tcx: TyCtxt<'tcx>,
359     ) -> ConstPropagator<'mir, 'tcx> {
360         let def_id = body.source.def_id();
361         let substs = &InternalSubsts::identity_for_item(tcx, def_id);
362         let param_env = tcx.param_env_reveal_all_normalized(def_id);
363
364         let span = tcx.def_span(def_id);
365         // FIXME: `CanConstProp::check` computes the layout of all locals, return those layouts
366         // so we can write them to `ecx.frame_mut().locals.layout, reducing the duplication in
367         // `layout_of` query invocations.
368         let can_const_prop = CanConstProp::check(tcx, param_env, body);
369         let mut only_propagate_inside_block_locals = BitSet::new_empty(can_const_prop.len());
370         for (l, mode) in can_const_prop.iter_enumerated() {
371             if *mode == ConstPropMode::OnlyInsideOwnBlock {
372                 only_propagate_inside_block_locals.insert(l);
373             }
374         }
375         let mut ecx = InterpCx::new(
376             tcx,
377             span,
378             param_env,
379             ConstPropMachine::new(only_propagate_inside_block_locals, can_const_prop),
380             (),
381         );
382
383         let ret = ecx
384             .layout_of(body.return_ty().subst(tcx, substs))
385             .ok()
386             // Don't bother allocating memory for ZST types which have no values
387             // or for large values.
388             .filter(|ret_layout| {
389                 !ret_layout.is_zst() && ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT)
390             })
391             .map(|ret_layout| {
392                 ecx.allocate(ret_layout, MemoryKind::Stack)
393                     .expect("couldn't perform small allocation")
394                     .into()
395             });
396
397         ecx.push_stack_frame(
398             Instance::new(def_id, substs),
399             dummy_body,
400             ret.as_ref(),
401             StackPopCleanup::Root { cleanup: false },
402         )
403         .expect("failed to push initial stack frame");
404
405         ConstPropagator {
406             ecx,
407             tcx,
408             param_env,
409             // FIXME(eddyb) avoid cloning this field more than once,
410             // by accessing it through `ecx` instead.
411             //FIXME(wesleywiser) we can't steal this because `Visitor::super_visit_body()` needs it
412             local_decls: body.local_decls.clone(),
413             source_info: None,
414         }
415     }
416
417     fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
418         let op = match self.ecx.eval_place_to_op(place, None) {
419             Ok(op) => op,
420             Err(e) => {
421                 trace!("get_const failed: {}", e);
422                 return None;
423             }
424         };
425
426         // Try to read the local as an immediate so that if it is representable as a scalar, we can
427         // handle it as such, but otherwise, just return the value as is.
428         Some(match self.ecx.try_read_immediate(&op) {
429             Ok(Ok(imm)) => imm.into(),
430             _ => op,
431         })
432     }
433
434     /// Remove `local` from the pool of `Locals`. Allows writing to them,
435     /// but not reading from them anymore.
436     fn remove_const(ecx: &mut InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, local: Local) {
437         ecx.frame_mut().locals[local] =
438             LocalState { value: LocalValue::Unallocated, layout: Cell::new(None) };
439     }
440
441     fn use_ecx<F, T>(&mut self, f: F) -> Option<T>
442     where
443         F: FnOnce(&mut Self) -> InterpResult<'tcx, T>,
444     {
445         match f(self) {
446             Ok(val) => Some(val),
447             Err(error) => {
448                 trace!("InterpCx operation failed: {:?}", error);
449                 // Some errors shouldn't come up because creating them causes
450                 // an allocation, which we should avoid. When that happens,
451                 // dedicated error variants should be introduced instead.
452                 assert!(
453                     !error.kind().formatted_string(),
454                     "const-prop encountered formatting error: {}",
455                     error
456                 );
457                 None
458             }
459         }
460     }
461
462     /// Returns the value, if any, of evaluating `c`.
463     fn eval_constant(&mut self, c: &Constant<'tcx>) -> Option<OpTy<'tcx>> {
464         // FIXME we need to revisit this for #67176
465         if c.needs_subst() {
466             return None;
467         }
468
469         self.ecx.mir_const_to_op(&c.literal, None).ok()
470     }
471
472     /// Returns the value, if any, of evaluating `place`.
473     fn eval_place(&mut self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
474         trace!("eval_place(place={:?})", place);
475         self.use_ecx(|this| this.ecx.eval_place_to_op(place, None))
476     }
477
478     /// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant`
479     /// or `eval_place`, depending on the variant of `Operand` used.
480     fn eval_operand(&mut self, op: &Operand<'tcx>) -> Option<OpTy<'tcx>> {
481         match *op {
482             Operand::Constant(ref c) => self.eval_constant(c),
483             Operand::Move(place) | Operand::Copy(place) => self.eval_place(place),
484         }
485     }
486
487     fn check_unary_op(&mut self, op: UnOp, arg: &Operand<'tcx>) -> Option<()> {
488         if self.use_ecx(|this| {
489             let val = this.ecx.read_immediate(&this.ecx.eval_operand(arg, None)?)?;
490             let (_res, overflow, _ty) = this.ecx.overflowing_unary_op(op, &val)?;
491             Ok(overflow)
492         })? {
493             // `AssertKind` only has an `OverflowNeg` variant, so make sure that is
494             // appropriate to use.
495             assert_eq!(op, UnOp::Neg, "Neg is the only UnOp that can overflow");
496             return None;
497         }
498
499         Some(())
500     }
501
502     fn check_binary_op(
503         &mut self,
504         op: BinOp,
505         left: &Operand<'tcx>,
506         right: &Operand<'tcx>,
507     ) -> Option<()> {
508         let r = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?));
509         let l = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?));
510         // Check for exceeding shifts *even if* we cannot evaluate the LHS.
511         if op == BinOp::Shr || op == BinOp::Shl {
512             let r = r?;
513             // We need the type of the LHS. We cannot use `place_layout` as that is the type
514             // of the result, which for checked binops is not the same!
515             let left_ty = left.ty(&self.local_decls, self.tcx);
516             let left_size = self.ecx.layout_of(left_ty).ok()?.size;
517             let right_size = r.layout.size;
518             let r_bits = r.to_scalar().ok();
519             let r_bits = r_bits.and_then(|r| r.to_bits(right_size).ok());
520             if r_bits.map_or(false, |b| b >= left_size.bits() as u128) {
521                 return None;
522             }
523         }
524
525         if let (Some(l), Some(r)) = (&l, &r) {
526             // The remaining operators are handled through `overflowing_binary_op`.
527             if self.use_ecx(|this| {
528                 let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?;
529                 Ok(overflow)
530             })? {
531                 return None;
532             }
533         }
534         Some(())
535     }
536
537     fn propagate_operand(&mut self, operand: &mut Operand<'tcx>) {
538         match *operand {
539             Operand::Copy(l) | Operand::Move(l) => {
540                 if let Some(value) = self.get_const(l) && self.should_const_prop(&value) {
541                     // FIXME(felix91gr): this code only handles `Scalar` cases.
542                     // For now, we're not handling `ScalarPair` cases because
543                     // doing so here would require a lot of code duplication.
544                     // We should hopefully generalize `Operand` handling into a fn,
545                     // and use it to do const-prop here and everywhere else
546                     // where it makes sense.
547                     if let interpret::Operand::Immediate(interpret::Immediate::Scalar(
548                         ScalarMaybeUninit::Scalar(scalar),
549                     )) = *value
550                     {
551                         *operand = self.operand_from_scalar(
552                             scalar,
553                             value.layout.ty,
554                             self.source_info.unwrap().span,
555                         );
556                     }
557                 }
558             }
559             Operand::Constant(_) => (),
560         }
561     }
562
563     fn const_prop(&mut self, rvalue: &Rvalue<'tcx>, place: Place<'tcx>) -> Option<()> {
564         // Perform any special handling for specific Rvalue types.
565         // Generally, checks here fall into one of two categories:
566         //   1. Additional checking to provide useful lints to the user
567         //        - In this case, we will do some validation and then fall through to the
568         //          end of the function which evals the assignment.
569         //   2. Working around bugs in other parts of the compiler
570         //        - In this case, we'll return `None` from this function to stop evaluation.
571         match rvalue {
572             // Additional checking: give lints to the user if an overflow would occur.
573             // We do this here and not in the `Assert` terminator as that terminator is
574             // only sometimes emitted (overflow checks can be disabled), but we want to always
575             // lint.
576             Rvalue::UnaryOp(op, arg) => {
577                 trace!("checking UnaryOp(op = {:?}, arg = {:?})", op, arg);
578                 self.check_unary_op(*op, arg)?;
579             }
580             Rvalue::BinaryOp(op, box (left, right)) => {
581                 trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right);
582                 self.check_binary_op(*op, left, right)?;
583             }
584             Rvalue::CheckedBinaryOp(op, box (left, right)) => {
585                 trace!(
586                     "checking CheckedBinaryOp(op = {:?}, left = {:?}, right = {:?})",
587                     op,
588                     left,
589                     right
590                 );
591                 self.check_binary_op(*op, left, right)?;
592             }
593
594             // Do not try creating references (#67862)
595             Rvalue::AddressOf(_, place) | Rvalue::Ref(_, _, place) => {
596                 trace!("skipping AddressOf | Ref for {:?}", place);
597
598                 // This may be creating mutable references or immutable references to cells.
599                 // If that happens, the pointed to value could be mutated via that reference.
600                 // Since we aren't tracking references, the const propagator loses track of what
601                 // value the local has right now.
602                 // Thus, all locals that have their reference taken
603                 // must not take part in propagation.
604                 Self::remove_const(&mut self.ecx, place.local);
605
606                 return None;
607             }
608             Rvalue::ThreadLocalRef(def_id) => {
609                 trace!("skipping ThreadLocalRef({:?})", def_id);
610
611                 return None;
612             }
613
614             // There's no other checking to do at this time.
615             Rvalue::Aggregate(..)
616             | Rvalue::Use(..)
617             | Rvalue::Repeat(..)
618             | Rvalue::Len(..)
619             | Rvalue::Cast(..)
620             | Rvalue::ShallowInitBox(..)
621             | Rvalue::Discriminant(..)
622             | Rvalue::NullaryOp(..) => {}
623         }
624
625         // FIXME we need to revisit this for #67176
626         if rvalue.needs_subst() {
627             return None;
628         }
629
630         if self.tcx.sess.mir_opt_level() >= 4 {
631             self.eval_rvalue_with_identities(rvalue, place)
632         } else {
633             self.use_ecx(|this| this.ecx.eval_rvalue_into_place(rvalue, place))
634         }
635     }
636
637     // Attempt to use algebraic identities to eliminate constant expressions
638     fn eval_rvalue_with_identities(
639         &mut self,
640         rvalue: &Rvalue<'tcx>,
641         place: Place<'tcx>,
642     ) -> Option<()> {
643         self.use_ecx(|this| match rvalue {
644             Rvalue::BinaryOp(op, box (left, right))
645             | Rvalue::CheckedBinaryOp(op, box (left, right)) => {
646                 let l = this.ecx.eval_operand(left, None);
647                 let r = this.ecx.eval_operand(right, None);
648
649                 let const_arg = match (l, r) {
650                     (Ok(ref x), Err(_)) | (Err(_), Ok(ref x)) => this.ecx.read_immediate(x)?,
651                     (Err(e), Err(_)) => return Err(e),
652                     (Ok(_), Ok(_)) => return this.ecx.eval_rvalue_into_place(rvalue, place),
653                 };
654
655                 let arg_value = const_arg.to_scalar()?.to_bits(const_arg.layout.size)?;
656                 let dest = this.ecx.eval_place(place)?;
657
658                 match op {
659                     BinOp::BitAnd if arg_value == 0 => this.ecx.write_immediate(*const_arg, &dest),
660                     BinOp::BitOr
661                         if arg_value == const_arg.layout.size.truncate(u128::MAX)
662                             || (const_arg.layout.ty.is_bool() && arg_value == 1) =>
663                     {
664                         this.ecx.write_immediate(*const_arg, &dest)
665                     }
666                     BinOp::Mul if const_arg.layout.ty.is_integral() && arg_value == 0 => {
667                         if let Rvalue::CheckedBinaryOp(_, _) = rvalue {
668                             let val = Immediate::ScalarPair(
669                                 const_arg.to_scalar()?.into(),
670                                 Scalar::from_bool(false).into(),
671                             );
672                             this.ecx.write_immediate(val, &dest)
673                         } else {
674                             this.ecx.write_immediate(*const_arg, &dest)
675                         }
676                     }
677                     _ => this.ecx.eval_rvalue_into_place(rvalue, place),
678                 }
679             }
680             _ => this.ecx.eval_rvalue_into_place(rvalue, place),
681         })
682     }
683
684     /// Creates a new `Operand::Constant` from a `Scalar` value
685     fn operand_from_scalar(&self, scalar: Scalar, ty: Ty<'tcx>, span: Span) -> Operand<'tcx> {
686         Operand::Constant(Box::new(Constant {
687             span,
688             user_ty: None,
689             literal: ty::Const::from_scalar(self.tcx, scalar, ty).into(),
690         }))
691     }
692
693     fn replace_with_const(
694         &mut self,
695         rval: &mut Rvalue<'tcx>,
696         value: &OpTy<'tcx>,
697         source_info: SourceInfo,
698     ) {
699         if let Rvalue::Use(Operand::Constant(c)) = rval {
700             match c.literal {
701                 ConstantKind::Ty(c) if matches!(c.val(), ConstKind::Unevaluated(..)) => {}
702                 _ => {
703                     trace!("skipping replace of Rvalue::Use({:?} because it is already a const", c);
704                     return;
705                 }
706             }
707         }
708
709         trace!("attempting to replace {:?} with {:?}", rval, value);
710         if let Err(e) = self.ecx.const_validate_operand(
711             value,
712             vec![],
713             // FIXME: is ref tracking too expensive?
714             // FIXME: what is the point of ref tracking if we do not even check the tracked refs?
715             &mut interpret::RefTracking::empty(),
716             CtfeValidationMode::Regular,
717         ) {
718             trace!("validation error, attempt failed: {:?}", e);
719             return;
720         }
721
722         // FIXME> figure out what to do when try_read_immediate fails
723         let imm = self.use_ecx(|this| this.ecx.try_read_immediate(value));
724
725         if let Some(Ok(imm)) = imm {
726             match *imm {
727                 interpret::Immediate::Scalar(ScalarMaybeUninit::Scalar(scalar)) => {
728                     *rval = Rvalue::Use(self.operand_from_scalar(
729                         scalar,
730                         value.layout.ty,
731                         source_info.span,
732                     ));
733                 }
734                 Immediate::ScalarPair(
735                     ScalarMaybeUninit::Scalar(_),
736                     ScalarMaybeUninit::Scalar(_),
737                 ) => {
738                     // Found a value represented as a pair. For now only do const-prop if the type
739                     // of `rvalue` is also a tuple with two scalars.
740                     // FIXME: enable the general case stated above ^.
741                     let ty = value.layout.ty;
742                     // Only do it for tuples
743                     if let ty::Tuple(types) = ty.kind() {
744                         // Only do it if tuple is also a pair with two scalars
745                         if let [ty1, ty2] = types[..] {
746                             let alloc = self.use_ecx(|this| {
747                                 let ty_is_scalar = |ty| {
748                                     this.ecx.layout_of(ty).ok().map(|layout| layout.abi.is_scalar())
749                                         == Some(true)
750                                 };
751                                 if ty_is_scalar(ty1) && ty_is_scalar(ty2) {
752                                     let alloc = this
753                                         .ecx
754                                         .intern_with_temp_alloc(value.layout, |ecx, dest| {
755                                             ecx.write_immediate(*imm, dest)
756                                         })
757                                         .unwrap();
758                                     Ok(Some(alloc))
759                                 } else {
760                                     Ok(None)
761                                 }
762                             });
763
764                             if let Some(Some(alloc)) = alloc {
765                                 // Assign entire constant in a single statement.
766                                 // We can't use aggregates, as we run after the aggregate-lowering `MirPhase`.
767                                 *rval = Rvalue::Use(Operand::Constant(Box::new(Constant {
768                                     span: source_info.span,
769                                     user_ty: None,
770                                     literal: self
771                                         .ecx
772                                         .tcx
773                                         .mk_const(ty::ConstS {
774                                             ty,
775                                             val: ty::ConstKind::Value(ConstValue::ByRef {
776                                                 alloc,
777                                                 offset: Size::ZERO,
778                                             }),
779                                         })
780                                         .into(),
781                                 })));
782                             }
783                         }
784                     }
785                 }
786                 // Scalars or scalar pairs that contain undef values are assumed to not have
787                 // successfully evaluated and are thus not propagated.
788                 _ => {}
789             }
790         }
791     }
792
793     /// Returns `true` if and only if this `op` should be const-propagated into.
794     fn should_const_prop(&mut self, op: &OpTy<'tcx>) -> bool {
795         let mir_opt_level = self.tcx.sess.mir_opt_level();
796
797         if mir_opt_level == 0 {
798             return false;
799         }
800
801         if !self.tcx.consider_optimizing(|| format!("ConstantPropagation - OpTy: {:?}", op)) {
802             return false;
803         }
804
805         match **op {
806             interpret::Operand::Immediate(Immediate::Scalar(ScalarMaybeUninit::Scalar(s))) => {
807                 s.try_to_int().is_ok()
808             }
809             interpret::Operand::Immediate(Immediate::ScalarPair(
810                 ScalarMaybeUninit::Scalar(l),
811                 ScalarMaybeUninit::Scalar(r),
812             )) => l.try_to_int().is_ok() && r.try_to_int().is_ok(),
813             _ => false,
814         }
815     }
816 }
817
818 /// The mode that `ConstProp` is allowed to run in for a given `Local`.
819 #[derive(Clone, Copy, Debug, PartialEq)]
820 enum ConstPropMode {
821     /// The `Local` can be propagated into and reads of this `Local` can also be propagated.
822     FullConstProp,
823     /// The `Local` can only be propagated into and from its own block.
824     OnlyInsideOwnBlock,
825     /// The `Local` can be propagated into but reads cannot be propagated.
826     OnlyPropagateInto,
827     /// The `Local` cannot be part of propagation at all. Any statement
828     /// referencing it either for reading or writing will not get propagated.
829     NoPropagation,
830 }
831
832 struct CanConstProp {
833     can_const_prop: IndexVec<Local, ConstPropMode>,
834     // False at the beginning. Once set, no more assignments are allowed to that local.
835     found_assignment: BitSet<Local>,
836     // Cache of locals' information
837     local_kinds: IndexVec<Local, LocalKind>,
838 }
839
840 impl CanConstProp {
841     /// Returns true if `local` can be propagated
842     fn check<'tcx>(
843         tcx: TyCtxt<'tcx>,
844         param_env: ParamEnv<'tcx>,
845         body: &Body<'tcx>,
846     ) -> IndexVec<Local, ConstPropMode> {
847         let mut cpv = CanConstProp {
848             can_const_prop: IndexVec::from_elem(ConstPropMode::FullConstProp, &body.local_decls),
849             found_assignment: BitSet::new_empty(body.local_decls.len()),
850             local_kinds: IndexVec::from_fn_n(
851                 |local| body.local_kind(local),
852                 body.local_decls.len(),
853             ),
854         };
855         for (local, val) in cpv.can_const_prop.iter_enumerated_mut() {
856             let ty = body.local_decls[local].ty;
857             match tcx.layout_of(param_env.and(ty)) {
858                 Ok(layout) if layout.size < Size::from_bytes(MAX_ALLOC_LIMIT) => {}
859                 // Either the layout fails to compute, then we can't use this local anyway
860                 // or the local is too large, then we don't want to.
861                 _ => {
862                     *val = ConstPropMode::NoPropagation;
863                     continue;
864                 }
865             }
866             // Cannot use args at all
867             // Cannot use locals because if x < y { y - x } else { x - y } would
868             //        lint for x != y
869             // FIXME(oli-obk): lint variables until they are used in a condition
870             // FIXME(oli-obk): lint if return value is constant
871             if cpv.local_kinds[local] == LocalKind::Arg {
872                 *val = ConstPropMode::OnlyPropagateInto;
873                 trace!(
874                     "local {:?} can't be const propagated because it's a function argument",
875                     local
876                 );
877             } else if cpv.local_kinds[local] == LocalKind::Var {
878                 *val = ConstPropMode::OnlyInsideOwnBlock;
879                 trace!(
880                     "local {:?} will only be propagated inside its block, because it's a user variable",
881                     local
882                 );
883             }
884         }
885         cpv.visit_body(&body);
886         cpv.can_const_prop
887     }
888 }
889
890 impl Visitor<'_> for CanConstProp {
891     fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) {
892         use rustc_middle::mir::visit::PlaceContext::*;
893         match context {
894             // Projections are fine, because `&mut foo.x` will be caught by
895             // `MutatingUseContext::Borrow` elsewhere.
896             MutatingUse(MutatingUseContext::Projection)
897             // These are just stores, where the storing is not propagatable, but there may be later
898             // mutations of the same local via `Store`
899             | MutatingUse(MutatingUseContext::Call)
900             | MutatingUse(MutatingUseContext::AsmOutput)
901             // Actual store that can possibly even propagate a value
902             | MutatingUse(MutatingUseContext::Store) => {
903                 if !self.found_assignment.insert(local) {
904                     match &mut self.can_const_prop[local] {
905                         // If the local can only get propagated in its own block, then we don't have
906                         // to worry about multiple assignments, as we'll nuke the const state at the
907                         // end of the block anyway, and inside the block we overwrite previous
908                         // states as applicable.
909                         ConstPropMode::OnlyInsideOwnBlock => {}
910                         ConstPropMode::NoPropagation => {}
911                         ConstPropMode::OnlyPropagateInto => {}
912                         other @ ConstPropMode::FullConstProp => {
913                             trace!(
914                                 "local {:?} can't be propagated because of multiple assignments. Previous state: {:?}",
915                                 local, other,
916                             );
917                             *other = ConstPropMode::OnlyInsideOwnBlock;
918                         }
919                     }
920                 }
921             }
922             // Reading constants is allowed an arbitrary number of times
923             NonMutatingUse(NonMutatingUseContext::Copy)
924             | NonMutatingUse(NonMutatingUseContext::Move)
925             | NonMutatingUse(NonMutatingUseContext::Inspect)
926             | NonMutatingUse(NonMutatingUseContext::Projection)
927             | NonUse(_) => {}
928
929             // These could be propagated with a smarter analysis or just some careful thinking about
930             // whether they'd be fine right now.
931             MutatingUse(MutatingUseContext::Yield)
932             | MutatingUse(MutatingUseContext::Drop)
933             | MutatingUse(MutatingUseContext::Retag)
934             // These can't ever be propagated under any scheme, as we can't reason about indirect
935             // mutation.
936             | NonMutatingUse(NonMutatingUseContext::SharedBorrow)
937             | NonMutatingUse(NonMutatingUseContext::ShallowBorrow)
938             | NonMutatingUse(NonMutatingUseContext::UniqueBorrow)
939             | NonMutatingUse(NonMutatingUseContext::AddressOf)
940             | MutatingUse(MutatingUseContext::Borrow)
941             | MutatingUse(MutatingUseContext::AddressOf) => {
942                 trace!("local {:?} can't be propagaged because it's used: {:?}", local, context);
943                 self.can_const_prop[local] = ConstPropMode::NoPropagation;
944             }
945         }
946     }
947 }
948
949 impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
950     fn tcx(&self) -> TyCtxt<'tcx> {
951         self.tcx
952     }
953
954     fn visit_body(&mut self, body: &mut Body<'tcx>) {
955         for (bb, data) in body.basic_blocks_mut().iter_enumerated_mut() {
956             self.visit_basic_block_data(bb, data);
957         }
958     }
959
960     fn visit_operand(&mut self, operand: &mut Operand<'tcx>, location: Location) {
961         self.super_operand(operand, location);
962
963         // Only const prop copies and moves on `mir_opt_level=3` as doing so
964         // currently slightly increases compile time in some cases.
965         if self.tcx.sess.mir_opt_level() >= 3 {
966             self.propagate_operand(operand)
967         }
968     }
969
970     fn visit_constant(&mut self, constant: &mut Constant<'tcx>, location: Location) {
971         trace!("visit_constant: {:?}", constant);
972         self.super_constant(constant, location);
973         self.eval_constant(constant);
974     }
975
976     fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
977         trace!("visit_statement: {:?}", statement);
978         let source_info = statement.source_info;
979         self.source_info = Some(source_info);
980         if let StatementKind::Assign(box (place, ref mut rval)) = statement.kind {
981             let can_const_prop = self.ecx.machine.can_const_prop[place.local];
982             if let Some(()) = self.const_prop(rval, place) {
983                 // This will return None if the above `const_prop` invocation only "wrote" a
984                 // type whose creation requires no write. E.g. a generator whose initial state
985                 // consists solely of uninitialized memory (so it doesn't capture any locals).
986                 if let Some(ref value) = self.get_const(place) && self.should_const_prop(value) {
987                     trace!("replacing {:?} with {:?}", rval, value);
988                     self.replace_with_const(rval, value, source_info);
989                     if can_const_prop == ConstPropMode::FullConstProp
990                         || can_const_prop == ConstPropMode::OnlyInsideOwnBlock
991                     {
992                         trace!("propagated into {:?}", place);
993                     }
994                 }
995                 match can_const_prop {
996                     ConstPropMode::OnlyInsideOwnBlock => {
997                         trace!(
998                             "found local restricted to its block. \
999                                 Will remove it from const-prop after block is finished. Local: {:?}",
1000                             place.local
1001                         );
1002                     }
1003                     ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
1004                         trace!("can't propagate into {:?}", place);
1005                         if place.local != RETURN_PLACE {
1006                             Self::remove_const(&mut self.ecx, place.local);
1007                         }
1008                     }
1009                     ConstPropMode::FullConstProp => {}
1010                 }
1011             } else {
1012                 // Const prop failed, so erase the destination, ensuring that whatever happens
1013                 // from here on, does not know about the previous value.
1014                 // This is important in case we have
1015                 // ```rust
1016                 // let mut x = 42;
1017                 // x = SOME_MUTABLE_STATIC;
1018                 // // x must now be uninit
1019                 // ```
1020                 // FIXME: we overzealously erase the entire local, because that's easier to
1021                 // implement.
1022                 trace!(
1023                     "propagation into {:?} failed.
1024                         Nuking the entire site from orbit, it's the only way to be sure",
1025                     place,
1026                 );
1027                 Self::remove_const(&mut self.ecx, place.local);
1028             }
1029         } else {
1030             match statement.kind {
1031                 StatementKind::SetDiscriminant { ref place, .. } => {
1032                     match self.ecx.machine.can_const_prop[place.local] {
1033                         ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => {
1034                             if self.use_ecx(|this| this.ecx.statement(statement)).is_some() {
1035                                 trace!("propped discriminant into {:?}", place);
1036                             } else {
1037                                 Self::remove_const(&mut self.ecx, place.local);
1038                             }
1039                         }
1040                         ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
1041                             Self::remove_const(&mut self.ecx, place.local);
1042                         }
1043                     }
1044                 }
1045                 StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
1046                     let frame = self.ecx.frame_mut();
1047                     frame.locals[local].value =
1048                         if let StatementKind::StorageLive(_) = statement.kind {
1049                             LocalValue::Unallocated
1050                         } else {
1051                             LocalValue::Dead
1052                         };
1053                 }
1054                 _ => {}
1055             }
1056         }
1057
1058         self.super_statement(statement, location);
1059     }
1060
1061     fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Location) {
1062         let source_info = terminator.source_info;
1063         self.source_info = Some(source_info);
1064         self.super_terminator(terminator, location);
1065         match &mut terminator.kind {
1066             TerminatorKind::Assert { expected, ref mut cond, .. } => {
1067                 if let Some(ref value) = self.eval_operand(&cond) {
1068                     trace!("assertion on {:?} should be {:?}", value, expected);
1069                     let expected = ScalarMaybeUninit::from(Scalar::from_bool(*expected));
1070                     let value_const = self.ecx.read_scalar(&value).unwrap();
1071                     if expected != value_const {
1072                         // Poison all places this operand references so that further code
1073                         // doesn't use the invalid value
1074                         match cond {
1075                             Operand::Move(ref place) | Operand::Copy(ref place) => {
1076                                 Self::remove_const(&mut self.ecx, place.local);
1077                             }
1078                             Operand::Constant(_) => {}
1079                         }
1080                     } else {
1081                         if self.should_const_prop(value) {
1082                             if let ScalarMaybeUninit::Scalar(scalar) = value_const {
1083                                 *cond = self.operand_from_scalar(
1084                                     scalar,
1085                                     self.tcx.types.bool,
1086                                     source_info.span,
1087                                 );
1088                             }
1089                         }
1090                     }
1091                 }
1092             }
1093             TerminatorKind::SwitchInt { ref mut discr, .. } => {
1094                 // FIXME: This is currently redundant with `visit_operand`, but sadly
1095                 // always visiting operands currently causes a perf regression in LLVM codegen, so
1096                 // `visit_operand` currently only runs for propagates places for `mir_opt_level=4`.
1097                 self.propagate_operand(discr)
1098             }
1099             // None of these have Operands to const-propagate.
1100             TerminatorKind::Goto { .. }
1101             | TerminatorKind::Resume
1102             | TerminatorKind::Abort
1103             | TerminatorKind::Return
1104             | TerminatorKind::Unreachable
1105             | TerminatorKind::Drop { .. }
1106             | TerminatorKind::DropAndReplace { .. }
1107             | TerminatorKind::Yield { .. }
1108             | TerminatorKind::GeneratorDrop
1109             | TerminatorKind::FalseEdge { .. }
1110             | TerminatorKind::FalseUnwind { .. }
1111             | TerminatorKind::InlineAsm { .. } => {}
1112             // Every argument in our function calls have already been propagated in `visit_operand`.
1113             //
1114             // NOTE: because LLVM codegen gives slight performance regressions with it, so this is
1115             // gated on `mir_opt_level=3`.
1116             TerminatorKind::Call { .. } => {}
1117         }
1118
1119         // We remove all Locals which are restricted in propagation to their containing blocks and
1120         // which were modified in the current block.
1121         // Take it out of the ecx so we can get a mutable reference to the ecx for `remove_const`.
1122         let mut locals = std::mem::take(&mut self.ecx.machine.written_only_inside_own_block_locals);
1123         for &local in locals.iter() {
1124             Self::remove_const(&mut self.ecx, local);
1125         }
1126         locals.clear();
1127         // Put it back so we reuse the heap of the storage
1128         self.ecx.machine.written_only_inside_own_block_locals = locals;
1129         if cfg!(debug_assertions) {
1130             // Ensure we are correctly erasing locals with the non-debug-assert logic.
1131             for local in self.ecx.machine.only_propagate_inside_block_locals.iter() {
1132                 assert!(
1133                     self.get_const(local.into()).is_none()
1134                         || self
1135                             .layout_of(self.local_decls[local].ty)
1136                             .map_or(true, |layout| layout.is_zst())
1137                 )
1138             }
1139         }
1140     }
1141 }