1 //! Propagates constants for early reporting of statically known
7 use rustc_ast::ast::Mutability;
8 use rustc_data_structures::fx::FxHashMap;
9 use rustc_hir::def::DefKind;
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,
16 use rustc_middle::mir::{
17 AggregateKind, AssertKind, BasicBlock, BinOp, Body, ClearCrossCrate, Constant, Local,
18 LocalDecl, LocalKind, Location, Operand, Place, Rvalue, SourceInfo, SourceScope,
19 SourceScopeData, Statement, StatementKind, Terminator, TerminatorKind, UnOp, RETURN_PLACE,
21 use rustc_middle::ty::layout::{HasTyCtxt, LayoutError, TyAndLayout};
22 use rustc_middle::ty::subst::{InternalSubsts, Subst};
23 use rustc_middle::ty::{self, ConstKind, Instance, ParamEnv, Ty, TyCtxt, TypeFoldable};
24 use rustc_session::lint;
25 use rustc_span::{def_id::DefId, Span};
26 use rustc_target::abi::{HasDataLayout, LayoutOf, Size, TargetDataLayout};
27 use rustc_trait_selection::traits;
29 use crate::const_eval::error_to_const_error;
30 use crate::interpret::{
31 self, intern_const_alloc_recursive, AllocId, Allocation, Frame, ImmTy, Immediate, InternKind,
32 InterpCx, LocalState, LocalValue, Memory, MemoryKind, OpTy, Operand as InterpOperand, PlaceTy,
33 Pointer, ScalarMaybeUndef, StackPopCleanup,
35 use crate::transform::{MirPass, MirSource};
37 /// The maximum number of bytes that we'll allocate space for a return value.
38 const MAX_ALLOC_LIMIT: u64 = 1024;
40 /// Macro for machine-specific `InterpError` without allocation.
41 /// (These will never be shown to the user, but they help diagnose ICEs.)
42 macro_rules! throw_machine_stop_str {
44 // We make a new local type for it. The type itself does not carry any information,
45 // but its vtable (for the `MachineStopType` trait) does.
47 // Debug-printing this type shows the desired string.
48 impl std::fmt::Debug for Zst {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 impl rustc_middle::mir::interpret::MachineStopType for Zst {}
54 throw_machine_stop!(Zst)
60 impl<'tcx> MirPass<'tcx> for ConstProp {
61 fn run_pass(&self, tcx: TyCtxt<'tcx>, source: MirSource<'tcx>, body: &mut Body<'tcx>) {
62 // will be evaluated by miri and produce its errors there
63 if source.promoted.is_some() {
67 use rustc_middle::hir::map::blocks::FnLikeNode;
70 .as_local_hir_id(source.def_id())
71 .expect("Non-local call to local provider is_const_fn");
73 let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();
74 let is_assoc_const = match tcx.def_kind(source.def_id()) {
75 Some(DefKind::AssocConst) => true,
79 // Only run const prop on functions, methods, closures and associated constants
80 if !is_fn_like && !is_assoc_const {
81 // skip anon_const/statics/consts because they'll be evaluated by miri anyway
82 trace!("ConstProp skipped for {:?}", source.def_id());
86 let is_generator = tcx.type_of(source.def_id()).is_generator();
87 // FIXME(welseywiser) const prop doesn't work on generators because of query cycles
88 // computing their layout.
90 trace!("ConstProp skipped for generator {:?}", source.def_id());
94 // Check if it's even possible to satisfy the 'where' clauses
96 // This branch will never be taken for any normal function.
97 // However, it's possible to `#!feature(trivial_bounds)]` to write
98 // a function with impossible to satisfy clauses, e.g.:
99 // `fn foo() where String: Copy {}`
101 // We don't usually need to worry about this kind of case,
102 // since we would get a compilation error if the user tried
103 // to call it. However, since we can do const propagation
104 // even without any calls to the function, we need to make
105 // sure that it even makes sense to try to evaluate the body.
106 // If there are unsatisfiable where clauses, then all bets are
107 // off, and we just give up.
109 // We manually filter the predicates, skipping anything that's not
110 // "global". We are in a potentially generic context
111 // (e.g. we are evaluating a function without substituting generic
112 // parameters, so this filtering serves two purposes:
114 // 1. We skip evaluating any predicates that we would
115 // never be able prove are unsatisfiable (e.g. `<T as Foo>`
116 // 2. We avoid trying to normalize predicates involving generic
117 // parameters (e.g. `<T as Foo>::MyItem`). This can confuse
118 // the normalization code (leading to cycle errors), since
119 // it's usually never invoked in this way.
121 .predicates_of(source.def_id())
124 .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None })
126 if !traits::normalize_and_test_predicates(
128 traits::elaborate_predicates(tcx, predicates).map(|o| o.predicate).collect(),
130 trace!("ConstProp skipped for {:?}: found unsatisfiable predicates", source.def_id());
134 trace!("ConstProp starting for {:?}", source.def_id());
136 let dummy_body = &Body::new(
137 body.basic_blocks().clone(),
138 body.source_scopes.clone(),
139 body.local_decls.clone(),
143 tcx.def_span(source.def_id()),
148 // FIXME(oli-obk, eddyb) Optimize locals (or even local paths) to hold
149 // constants, instead of just checking for const-folding succeeding.
150 // That would require an uniform one-def no-mutation analysis
151 // and RPO (or recursing when needing the value of a local).
152 let mut optimization_finder = ConstPropagator::new(body, dummy_body, tcx, source);
153 optimization_finder.visit_body(body);
155 trace!("ConstProp done for {:?}", source.def_id());
159 struct ConstPropMachine<'mir, 'tcx> {
160 /// The virtual call stack.
161 stack: Vec<Frame<'mir, 'tcx, (), ()>>,
164 impl<'mir, 'tcx> ConstPropMachine<'mir, 'tcx> {
166 Self { stack: Vec::new() }
170 impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx> {
172 type PointerTag = ();
175 type FrameExtra = ();
176 type MemoryExtra = ();
177 type AllocExtra = ();
179 type MemoryMap = FxHashMap<AllocId, (MemoryKind<!>, Allocation)>;
181 const GLOBAL_KIND: Option<!> = None; // no copying of globals from `tcx` to machine memory
184 fn enforce_alignment(_memory_extra: &Self::MemoryExtra) -> bool {
189 fn enforce_validity(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
193 fn find_mir_or_eval_fn(
194 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
195 _instance: ty::Instance<'tcx>,
196 _args: &[OpTy<'tcx>],
197 _ret: Option<(PlaceTy<'tcx>, BasicBlock)>,
198 _unwind: Option<BasicBlock>,
199 ) -> InterpResult<'tcx, Option<&'mir Body<'tcx>>> {
204 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
206 _args: &[OpTy<'tcx>],
207 _ret: Option<(PlaceTy<'tcx>, BasicBlock)>,
208 _unwind: Option<BasicBlock>,
209 ) -> InterpResult<'tcx> {
214 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
215 _instance: ty::Instance<'tcx>,
216 _args: &[OpTy<'tcx>],
217 _ret: Option<(PlaceTy<'tcx>, BasicBlock)>,
218 _unwind: Option<BasicBlock>,
219 ) -> InterpResult<'tcx> {
220 throw_machine_stop_str!("calling intrinsics isn't supported in ConstProp")
224 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
225 _msg: &rustc_middle::mir::AssertMessage<'tcx>,
226 _unwind: Option<rustc_middle::mir::BasicBlock>,
227 ) -> InterpResult<'tcx> {
228 bug!("panics terminators are not evaluated in ConstProp")
231 fn ptr_to_int(_mem: &Memory<'mir, 'tcx, Self>, _ptr: Pointer) -> InterpResult<'tcx, u64> {
232 throw_unsup!(ReadPointerAsBytes)
236 _ecx: &InterpCx<'mir, 'tcx, Self>,
240 ) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)> {
241 // We can't do this because aliasing of memory can differ between const eval and llvm
242 throw_machine_stop_str!("pointer arithmetic or comparisons aren't supported in ConstProp")
246 fn init_allocation_extra<'b>(
249 alloc: Cow<'b, Allocation>,
250 _kind: Option<MemoryKind<!>>,
251 ) -> (Cow<'b, Allocation<Self::PointerTag>>, Self::PointerTag) {
252 // We do not use a tag so we can just cheaply forward the allocation
257 fn tag_global_base_pointer(_memory_extra: &(), _id: AllocId) -> Self::PointerTag {}
260 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
261 _dest: PlaceTy<'tcx>,
262 ) -> InterpResult<'tcx> {
263 throw_machine_stop_str!("can't const prop heap allocations")
267 _ecx: &InterpCx<'mir, 'tcx, Self>,
268 frame: &Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
270 ) -> InterpResult<'tcx, InterpOperand<Self::PointerTag>> {
271 let l = &frame.locals[local];
273 if l.value == LocalValue::Uninitialized {
274 throw_machine_stop_str!("tried to access an uninitialized local")
280 fn before_access_global(
283 allocation: &Allocation<Self::PointerTag, Self::AllocExtra>,
284 _static_def_id: Option<DefId>,
286 ) -> InterpResult<'tcx> {
288 throw_machine_stop_str!("can't write to global");
290 // If the static allocation is mutable, then we can't const prop it as its content
291 // might be different at runtime.
292 if allocation.mutability == Mutability::Mut {
293 throw_machine_stop_str!("can't access mutable globals in ConstProp");
301 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
302 frame: Frame<'mir, 'tcx>,
303 ) -> InterpResult<'tcx, Frame<'mir, 'tcx>> {
309 ecx: &'a InterpCx<'mir, 'tcx, Self>,
310 ) -> &'a [Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>] {
316 ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
317 ) -> &'a mut Vec<Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>> {
318 &mut ecx.machine.stack
322 /// Finds optimization opportunities on the MIR.
323 struct ConstPropagator<'mir, 'tcx> {
324 ecx: InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>,
326 can_const_prop: IndexVec<Local, ConstPropMode>,
327 param_env: ParamEnv<'tcx>,
328 // FIXME(eddyb) avoid cloning these two fields more than once,
329 // by accessing them through `ecx` instead.
330 source_scopes: IndexVec<SourceScope, SourceScopeData>,
331 local_decls: IndexVec<Local, LocalDecl<'tcx>>,
332 ret: Option<OpTy<'tcx, ()>>,
333 // Because we have `MutVisitor` we can't obtain the `SourceInfo` from a `Location`. So we store
334 // the last known `SourceInfo` here and just keep revisiting it.
335 source_info: Option<SourceInfo>,
338 impl<'mir, 'tcx> LayoutOf for ConstPropagator<'mir, 'tcx> {
340 type TyAndLayout = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>;
342 fn layout_of(&self, ty: Ty<'tcx>) -> Self::TyAndLayout {
343 self.tcx.layout_of(self.param_env.and(ty))
347 impl<'mir, 'tcx> HasDataLayout for ConstPropagator<'mir, 'tcx> {
349 fn data_layout(&self) -> &TargetDataLayout {
350 &self.tcx.data_layout
354 impl<'mir, 'tcx> HasTyCtxt<'tcx> for ConstPropagator<'mir, 'tcx> {
356 fn tcx(&self) -> TyCtxt<'tcx> {
361 impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
364 dummy_body: &'mir Body<'tcx>,
366 source: MirSource<'tcx>,
367 ) -> ConstPropagator<'mir, 'tcx> {
368 let def_id = source.def_id();
369 let substs = &InternalSubsts::identity_for_item(tcx, def_id);
370 let param_env = tcx.param_env(def_id).with_reveal_all();
372 let span = tcx.def_span(def_id);
373 let mut ecx = InterpCx::new(tcx.at(span), param_env, ConstPropMachine::new(), ());
374 let can_const_prop = CanConstProp::check(body);
377 .layout_of(body.return_ty().subst(tcx, substs))
379 // Don't bother allocating memory for ZST types which have no values
380 // or for large values.
381 .filter(|ret_layout| {
382 !ret_layout.is_zst() && ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT)
384 .map(|ret_layout| ecx.allocate(ret_layout, MemoryKind::Stack));
386 ecx.push_stack_frame(
387 Instance::new(def_id, substs),
390 StackPopCleanup::None { cleanup: false },
392 .expect("failed to push initial stack frame");
399 // FIXME(eddyb) avoid cloning these two fields more than once,
400 // by accessing them through `ecx` instead.
401 source_scopes: body.source_scopes.clone(),
402 //FIXME(wesleywiser) we can't steal this because `Visitor::super_visit_body()` needs it
403 local_decls: body.local_decls.clone(),
404 ret: ret.map(Into::into),
409 fn get_const(&self, local: Local) -> Option<OpTy<'tcx>> {
410 if local == RETURN_PLACE {
411 // Try to read the return place as an immediate so that if it is representable as a
412 // scalar, we can handle it as such, but otherwise, just return the value as is.
413 return match self.ret.map(|ret| self.ecx.try_read_immediate(ret)) {
414 Some(Ok(Ok(imm))) => Some(imm.into()),
419 self.ecx.access_local(self.ecx.frame(), local, None).ok()
422 fn remove_const(&mut self, local: Local) {
423 self.ecx.frame_mut().locals[local] =
424 LocalState { value: LocalValue::Uninitialized, layout: Cell::new(None) };
427 fn lint_root(&self, source_info: SourceInfo) -> Option<HirId> {
428 match &self.source_scopes[source_info.scope].local_data {
429 ClearCrossCrate::Set(data) => Some(data.lint_root),
430 ClearCrossCrate::Clear => None,
434 fn use_ecx<F, T>(&mut self, f: F) -> Option<T>
436 F: FnOnce(&mut Self) -> InterpResult<'tcx, T>,
439 Ok(val) => Some(val),
441 // Some errors shouldn't come up because creating them causes
442 // an allocation, which we should avoid. When that happens,
443 // dedicated error variants should be introduced instead.
445 !error.kind.allocates(),
446 "const-prop encountered allocating error: {}",
454 fn eval_constant(&mut self, c: &Constant<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
455 // FIXME we need to revisit this for #67176
460 match self.ecx.eval_const_to_op(c.literal, None) {
463 // Make sure errors point at the constant.
464 self.ecx.set_span(c.span);
465 let err = error_to_const_error(&self.ecx, error);
466 if let Some(lint_root) = self.lint_root(source_info) {
467 let lint_only = match c.literal.val {
468 // Promoteds must lint and not error as the user didn't ask for them
469 ConstKind::Unevaluated(_, _, Some(_)) => true,
470 // Out of backwards compatibility we cannot report hard errors in unused
471 // generic functions using associated constants of the generic parameters.
472 _ => c.literal.needs_subst(),
475 // Out of backwards compatibility we cannot report hard errors in unused
476 // generic functions using associated constants of the generic parameters.
479 "erroneous constant used",
484 err.report_as_error(self.ecx.tcx, "erroneous constant used");
487 err.report_as_error(self.ecx.tcx, "erroneous constant used");
494 fn eval_place(&mut self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
495 trace!("eval_place(place={:?})", place);
496 self.use_ecx(|this| this.ecx.eval_place_to_op(place, None))
499 fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
501 Operand::Constant(ref c) => self.eval_constant(c, source_info),
502 Operand::Move(place) | Operand::Copy(place) => self.eval_place(place),
506 fn report_assert_as_lint(
508 lint: &'static lint::Lint,
509 source_info: SourceInfo,
510 message: &'static str,
511 panic: AssertKind<u64>,
513 let lint_root = self.lint_root(source_info)?;
514 self.tcx.struct_span_lint_hir(lint, lint_root, source_info.span, |lint| {
515 let mut err = lint.build(message);
516 err.span_label(source_info.span, format!("{:?}", panic));
526 source_info: SourceInfo,
528 if self.use_ecx(|this| {
529 let val = this.ecx.read_immediate(this.ecx.eval_operand(arg, None)?)?;
530 let (_res, overflow, _ty) = this.ecx.overflowing_unary_op(op, val)?;
533 // `AssertKind` only has an `OverflowNeg` variant, so make sure that is
534 // appropriate to use.
535 assert_eq!(op, UnOp::Neg, "Neg is the only UnOp that can overflow");
536 self.report_assert_as_lint(
537 lint::builtin::ARITHMETIC_OVERFLOW,
539 "this arithmetic operation will overflow",
540 AssertKind::OverflowNeg,
550 left: &Operand<'tcx>,
551 right: &Operand<'tcx>,
552 source_info: SourceInfo,
555 self.use_ecx(|this| this.ecx.read_immediate(this.ecx.eval_operand(right, None)?))?;
556 // Check for exceeding shifts *even if* we cannot evaluate the LHS.
557 if op == BinOp::Shr || op == BinOp::Shl {
558 // We need the type of the LHS. We cannot use `place_layout` as that is the type
559 // of the result, which for checked binops is not the same!
560 let left_ty = left.ty(&self.local_decls, self.tcx);
561 let left_size_bits = self.ecx.layout_of(left_ty).ok()?.size.bits();
562 let right_size = r.layout.size;
563 let r_bits = r.to_scalar().ok();
564 // This is basically `force_bits`.
565 let r_bits = r_bits.and_then(|r| r.to_bits_or_ptr(right_size, &self.tcx).ok());
566 if r_bits.map_or(false, |b| b >= left_size_bits as u128) {
567 self.report_assert_as_lint(
568 lint::builtin::ARITHMETIC_OVERFLOW,
570 "this arithmetic operation will overflow",
571 AssertKind::Overflow(op),
576 // The remaining operators are handled through `overflowing_binary_op`.
577 if self.use_ecx(|this| {
578 let l = this.ecx.read_immediate(this.ecx.eval_operand(left, None)?)?;
579 let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?;
582 self.report_assert_as_lint(
583 lint::builtin::ARITHMETIC_OVERFLOW,
585 "this arithmetic operation will overflow",
586 AssertKind::Overflow(op),
595 rvalue: &Rvalue<'tcx>,
596 place_layout: TyAndLayout<'tcx>,
597 source_info: SourceInfo,
600 // #66397: Don't try to eval into large places as that can cause an OOM
601 if place_layout.size >= Size::from_bytes(MAX_ALLOC_LIMIT) {
605 // Perform any special handling for specific Rvalue types.
606 // Generally, checks here fall into one of two categories:
607 // 1. Additional checking to provide useful lints to the user
608 // - In this case, we will do some validation and then fall through to the
609 // end of the function which evals the assignment.
610 // 2. Working around bugs in other parts of the compiler
611 // - In this case, we'll return `None` from this function to stop evaluation.
613 // Additional checking: give lints to the user if an overflow would occur.
614 // We do this here and not in the `Assert` terminator as that terminator is
615 // only sometimes emitted (overflow checks can be disabled), but we want to always
617 Rvalue::UnaryOp(op, arg) => {
618 trace!("checking UnaryOp(op = {:?}, arg = {:?})", op, arg);
619 self.check_unary_op(*op, arg, source_info)?;
621 Rvalue::BinaryOp(op, left, right) => {
622 trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right);
623 self.check_binary_op(*op, left, right, source_info)?;
625 Rvalue::CheckedBinaryOp(op, left, right) => {
627 "checking CheckedBinaryOp(op = {:?}, left = {:?}, right = {:?})",
632 self.check_binary_op(*op, left, right, source_info)?;
635 // Do not try creating references (#67862)
636 Rvalue::Ref(_, _, place_ref) => {
637 trace!("skipping Ref({:?})", place_ref);
645 // FIXME we need to revisit this for #67176
646 if rvalue.needs_subst() {
650 self.use_ecx(|this| {
651 trace!("calling eval_rvalue_into_place(rvalue = {:?}, place = {:?})", rvalue, place);
652 this.ecx.eval_rvalue_into_place(rvalue, place)?;
657 fn operand_from_scalar(&self, scalar: Scalar, ty: Ty<'tcx>, span: Span) -> Operand<'tcx> {
658 Operand::Constant(Box::new(Constant {
661 literal: self.tcx.mk_const(*ty::Const::from_scalar(self.tcx, scalar, ty)),
665 fn replace_with_const(
667 rval: &mut Rvalue<'tcx>,
669 source_info: SourceInfo,
671 trace!("attepting to replace {:?} with {:?}", rval, value);
672 if let Err(e) = self.ecx.const_validate_operand(
675 // FIXME: is ref tracking too expensive?
676 &mut interpret::RefTracking::empty(),
677 /*may_ref_to_static*/ true,
679 trace!("validation error, attempt failed: {:?}", e);
683 // FIXME> figure out what to do when try_read_immediate fails
684 let imm = self.use_ecx(|this| this.ecx.try_read_immediate(value));
686 if let Some(Ok(imm)) = imm {
688 interpret::Immediate::Scalar(ScalarMaybeUndef::Scalar(scalar)) => {
689 *rval = Rvalue::Use(self.operand_from_scalar(
695 Immediate::ScalarPair(
696 ScalarMaybeUndef::Scalar(one),
697 ScalarMaybeUndef::Scalar(two),
699 // Found a value represented as a pair. For now only do cont-prop if type of
700 // Rvalue is also a pair with two scalars. The more general case is more
701 // complicated to implement so we'll do it later.
702 let ty = &value.layout.ty.kind;
703 // Only do it for tuples
704 if let ty::Tuple(substs) = ty {
705 // Only do it if tuple is also a pair with two scalars
706 if substs.len() == 2 {
707 let opt_ty1_ty2 = self.use_ecx(|this| {
708 let ty1 = substs[0].expect_ty();
709 let ty2 = substs[1].expect_ty();
710 let ty_is_scalar = |ty| {
711 this.ecx.layout_of(ty).ok().map(|layout| layout.abi.is_scalar())
714 if ty_is_scalar(ty1) && ty_is_scalar(ty2) {
721 if let Some(Some((ty1, ty2))) = opt_ty1_ty2 {
722 *rval = Rvalue::Aggregate(
723 Box::new(AggregateKind::Tuple),
725 self.operand_from_scalar(one, ty1, source_info.span),
726 self.operand_from_scalar(two, ty2, source_info.span),
738 fn should_const_prop(&mut self, op: OpTy<'tcx>) -> bool {
739 let mir_opt_level = self.tcx.sess.opts.debugging_opts.mir_opt_level;
741 if mir_opt_level == 0 {
746 interpret::Operand::Immediate(Immediate::Scalar(ScalarMaybeUndef::Scalar(s))) => {
749 interpret::Operand::Immediate(Immediate::ScalarPair(
750 ScalarMaybeUndef::Scalar(l),
751 ScalarMaybeUndef::Scalar(r),
752 )) => l.is_bits() && r.is_bits(),
753 interpret::Operand::Indirect(_) if mir_opt_level >= 2 => {
754 let mplace = op.assert_mem_place(&self.ecx);
755 intern_const_alloc_recursive(&mut self.ecx, InternKind::ConstProp, mplace, false)
756 .expect("failed to intern alloc");
764 /// The mode that `ConstProp` is allowed to run in for a given `Local`.
765 #[derive(Clone, Copy, Debug, PartialEq)]
767 /// The `Local` can be propagated into and reads of this `Local` can also be propagated.
769 /// The `Local` can be propagated into but reads cannot be propagated.
771 /// No propagation is allowed at all.
775 struct CanConstProp {
776 can_const_prop: IndexVec<Local, ConstPropMode>,
777 // false at the beginning, once set, there are not allowed to be any more assignments
778 found_assignment: IndexVec<Local, bool>,
782 /// returns true if `local` can be propagated
783 fn check(body: &Body<'_>) -> IndexVec<Local, ConstPropMode> {
784 let mut cpv = CanConstProp {
785 can_const_prop: IndexVec::from_elem(ConstPropMode::FullConstProp, &body.local_decls),
786 found_assignment: IndexVec::from_elem(false, &body.local_decls),
788 for (local, val) in cpv.can_const_prop.iter_enumerated_mut() {
789 // cannot use args at all
790 // cannot use locals because if x < y { y - x } else { x - y } would
792 // FIXME(oli-obk): lint variables until they are used in a condition
793 // FIXME(oli-obk): lint if return value is constant
794 let local_kind = body.local_kind(local);
796 if local_kind == LocalKind::Arg || local_kind == LocalKind::Var {
797 *val = ConstPropMode::OnlyPropagateInto;
798 trace!("local {:?} can't be const propagated because it's not a temporary", local);
801 cpv.visit_body(&body);
806 impl<'tcx> Visitor<'tcx> for CanConstProp {
807 fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) {
808 use rustc_middle::mir::visit::PlaceContext::*;
810 // Constants must have at most one write
811 // FIXME(oli-obk): we could be more powerful here, if the multiple writes
812 // only occur in independent execution paths
813 MutatingUse(MutatingUseContext::Store) => {
814 if self.found_assignment[local] {
815 trace!("local {:?} can't be propagated because of multiple assignments", local);
816 self.can_const_prop[local] = ConstPropMode::NoPropagation;
818 self.found_assignment[local] = true
821 // Reading constants is allowed an arbitrary number of times
822 NonMutatingUse(NonMutatingUseContext::Copy)
823 | NonMutatingUse(NonMutatingUseContext::Move)
824 | NonMutatingUse(NonMutatingUseContext::Inspect)
825 | NonMutatingUse(NonMutatingUseContext::Projection)
826 | MutatingUse(MutatingUseContext::Projection)
829 trace!("local {:?} can't be propagaged because it's used: {:?}", local, context);
830 self.can_const_prop[local] = ConstPropMode::NoPropagation;
836 impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> {
837 fn tcx(&self) -> TyCtxt<'tcx> {
841 fn visit_constant(&mut self, constant: &mut Constant<'tcx>, location: Location) {
842 trace!("visit_constant: {:?}", constant);
843 self.super_constant(constant, location);
844 self.eval_constant(constant, self.source_info.unwrap());
847 fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
848 trace!("visit_statement: {:?}", statement);
849 let source_info = statement.source_info;
850 self.ecx.set_span(source_info.span);
851 self.source_info = Some(source_info);
852 if let StatementKind::Assign(box (place, ref mut rval)) = statement.kind {
853 let place_ty: Ty<'tcx> = place.ty(&self.local_decls, self.tcx).ty;
854 if let Ok(place_layout) = self.tcx.layout_of(self.param_env.and(place_ty)) {
855 if let Some(local) = place.as_local() {
856 let can_const_prop = self.can_const_prop[local];
857 if let Some(()) = self.const_prop(rval, place_layout, source_info, place) {
858 if can_const_prop == ConstPropMode::FullConstProp
859 || can_const_prop == ConstPropMode::OnlyPropagateInto
861 if let Some(value) = self.get_const(local) {
862 if self.should_const_prop(value) {
863 trace!("replacing {:?} with {:?}", rval, value);
864 self.replace_with_const(rval, value, statement.source_info);
866 if can_const_prop == ConstPropMode::FullConstProp {
867 trace!("propagated into {:?}", local);
873 if self.can_const_prop[local] != ConstPropMode::FullConstProp {
874 trace!("can't propagate into {:?}", local);
875 if local != RETURN_PLACE {
876 self.remove_const(local);
882 match statement.kind {
883 StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
884 let frame = self.ecx.frame_mut();
885 frame.locals[local].value =
886 if let StatementKind::StorageLive(_) = statement.kind {
887 LocalValue::Uninitialized
896 self.super_statement(statement, location);
899 fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Location) {
900 let source_info = terminator.source_info;
901 self.ecx.set_span(source_info.span);
902 self.source_info = Some(source_info);
903 self.super_terminator(terminator, location);
904 match &mut terminator.kind {
905 TerminatorKind::Assert { expected, ref msg, ref mut cond, .. } => {
906 if let Some(value) = self.eval_operand(&cond, source_info) {
907 trace!("assertion on {:?} should be {:?}", value, expected);
908 let expected = ScalarMaybeUndef::from(Scalar::from_bool(*expected));
909 let value_const = self.ecx.read_scalar(value).unwrap();
910 if expected != value_const {
911 // poison all places this operand references so that further code
912 // doesn't use the invalid value
914 Operand::Move(ref place) | Operand::Copy(ref place) => {
915 self.remove_const(place.local);
917 Operand::Constant(_) => {}
919 let msg = match msg {
920 AssertKind::DivisionByZero => AssertKind::DivisionByZero,
921 AssertKind::RemainderByZero => AssertKind::RemainderByZero,
922 AssertKind::BoundsCheck { ref len, ref index } => {
924 self.eval_operand(len, source_info).expect("len must be const");
929 .to_machine_usize(&self.tcx)
932 .eval_operand(index, source_info)
933 .expect("index must be const");
938 .to_machine_usize(&self.tcx)
940 AssertKind::BoundsCheck { len, index }
942 // Overflow is are already covered by checks on the binary operators.
943 AssertKind::Overflow(_) | AssertKind::OverflowNeg => return,
944 // Need proper const propagator for these.
947 self.report_assert_as_lint(
948 lint::builtin::UNCONDITIONAL_PANIC,
950 "this operation will panic at runtime",
954 if self.should_const_prop(value) {
955 if let ScalarMaybeUndef::Scalar(scalar) = value_const {
956 *cond = self.operand_from_scalar(
966 TerminatorKind::SwitchInt { ref mut discr, switch_ty, .. } => {
967 if let Some(value) = self.eval_operand(&discr, source_info) {
968 if self.should_const_prop(value) {
969 if let ScalarMaybeUndef::Scalar(scalar) =
970 self.ecx.read_scalar(value).unwrap()
972 *discr = self.operand_from_scalar(scalar, switch_ty, source_info.span);
977 //none of these have Operands to const-propagate
978 TerminatorKind::Goto { .. }
979 | TerminatorKind::Resume
980 | TerminatorKind::Abort
981 | TerminatorKind::Return
982 | TerminatorKind::Unreachable
983 | TerminatorKind::Drop { .. }
984 | TerminatorKind::DropAndReplace { .. }
985 | TerminatorKind::Yield { .. }
986 | TerminatorKind::GeneratorDrop
987 | TerminatorKind::FalseEdges { .. }
988 | TerminatorKind::FalseUnwind { .. } => {}
989 //FIXME(wesleywiser) Call does have Operands that could be const-propagated
990 TerminatorKind::Call { .. } => {}