1 //! Propagates constants for early reporting of statically known
6 use rustc_ast
::Mutability
;
7 use rustc_data_structures
::fx
::FxHashSet
;
8 use rustc_hir
::def
::DefKind
;
10 use rustc_index
::bit_set
::BitSet
;
11 use rustc_index
::vec
::IndexVec
;
12 use rustc_middle
::mir
::visit
::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor}
;
13 use rustc_middle
::mir
::{
14 AssertKind
, BasicBlock
, BinOp
, Body
, Constant
, ConstantKind
, Local
, LocalDecl
, LocalKind
,
15 Location
, Operand
, Place
, Rvalue
, SourceInfo
, SourceScope
, SourceScopeData
, Statement
,
16 StatementKind
, Terminator
, TerminatorKind
, UnOp
, RETURN_PLACE
,
18 use rustc_middle
::ty
::layout
::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout}
;
19 use rustc_middle
::ty
::subst
::{InternalSubsts, Subst}
;
20 use rustc_middle
::ty
::{
21 self, ConstInt
, ConstKind
, EarlyBinder
, Instance
, ParamEnv
, ScalarInt
, Ty
, TyCtxt
, TypeFoldable
,
23 use rustc_session
::lint
;
24 use rustc_span
::{def_id::DefId, Span}
;
25 use rustc_target
::abi
::{HasDataLayout, Size, TargetDataLayout}
;
26 use rustc_target
::spec
::abi
::Abi
;
27 use rustc_trait_selection
::traits
;
30 use rustc_const_eval
::const_eval
::ConstEvalErr
;
31 use rustc_const_eval
::interpret
::{
32 self, compile_time_machine
, AllocId
, ConstAllocation
, Frame
, ImmTy
, InterpCx
, InterpResult
,
33 LocalState
, LocalValue
, MemPlace
, MemoryKind
, OpTy
, Operand
as InterpOperand
, PlaceTy
, Pointer
,
34 Scalar
, ScalarMaybeUninit
, StackPopCleanup
, StackPopUnwind
,
37 /// The maximum number of bytes that we'll allocate space for a local or the return value.
38 /// Needed for #66397, because otherwise we eval into large places and that can cause OOM or just
39 /// Severely regress performance.
40 const MAX_ALLOC_LIMIT
: u64 = 1024;
42 /// Macro for machine-specific `InterpError` without allocation.
43 /// (These will never be shown to the user, but they help diagnose ICEs.)
44 macro_rules
! throw_machine_stop_str
{
46 // We make a new local type for it. The type itself does not carry any information,
47 // but its vtable (for the `MachineStopType` trait) does.
49 // Printing this type shows the desired string.
50 impl std
::fmt
::Display
for Zst
{
51 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
55 impl rustc_middle
::mir
::interpret
::MachineStopType
for Zst {}
56 throw_machine_stop
!(Zst
)
62 impl<'tcx
> MirLint
<'tcx
> for ConstProp
{
63 fn run_lint(&self, tcx
: TyCtxt
<'tcx
>, body
: &Body
<'tcx
>) {
64 // will be evaluated by miri and produce its errors there
65 if body
.source
.promoted
.is_some() {
69 let def_id
= body
.source
.def_id().expect_local();
70 let is_fn_like
= tcx
.def_kind(def_id
).is_fn_like();
71 let is_assoc_const
= tcx
.def_kind(def_id
) == DefKind
::AssocConst
;
73 // Only run const prop on functions, methods, closures and associated constants
74 if !is_fn_like
&& !is_assoc_const
{
75 // skip anon_const/statics/consts because they'll be evaluated by miri anyway
76 trace
!("ConstProp skipped for {:?}", def_id
);
80 let is_generator
= tcx
.type_of(def_id
.to_def_id()).is_generator();
81 // FIXME(welseywiser) const prop doesn't work on generators because of query cycles
82 // computing their layout.
84 trace
!("ConstProp skipped for generator {:?}", def_id
);
88 // Check if it's even possible to satisfy the 'where' clauses
90 // This branch will never be taken for any normal function.
91 // However, it's possible to `#!feature(trivial_bounds)]` to write
92 // a function with impossible to satisfy clauses, e.g.:
93 // `fn foo() where String: Copy {}`
95 // We don't usually need to worry about this kind of case,
96 // since we would get a compilation error if the user tried
97 // to call it. However, since we can do const propagation
98 // even without any calls to the function, we need to make
99 // sure that it even makes sense to try to evaluate the body.
100 // If there are unsatisfiable where clauses, then all bets are
101 // off, and we just give up.
103 // We manually filter the predicates, skipping anything that's not
104 // "global". We are in a potentially generic context
105 // (e.g. we are evaluating a function without substituting generic
106 // parameters, so this filtering serves two purposes:
108 // 1. We skip evaluating any predicates that we would
109 // never be able prove are unsatisfiable (e.g. `<T as Foo>`
110 // 2. We avoid trying to normalize predicates involving generic
111 // parameters (e.g. `<T as Foo>::MyItem`). This can confuse
112 // the normalization code (leading to cycle errors), since
113 // it's usually never invoked in this way.
115 .predicates_of(def_id
.to_def_id())
118 .filter_map(|(p
, _
)| if p
.is_global() { Some(*p) }
else { None }
);
119 if traits
::impossible_predicates(
121 traits
::elaborate_predicates(tcx
, predicates
).map(|o
| o
.predicate
).collect(),
123 trace
!("ConstProp skipped for {:?}: found unsatisfiable predicates", def_id
);
127 trace
!("ConstProp starting for {:?}", def_id
);
129 let dummy_body
= &Body
::new(
131 body
.basic_blocks().clone(),
132 body
.source_scopes
.clone(),
133 body
.local_decls
.clone(),
138 body
.generator_kind(),
139 body
.tainted_by_errors
,
142 // FIXME(oli-obk, eddyb) Optimize locals (or even local paths) to hold
143 // constants, instead of just checking for const-folding succeeding.
144 // That would require a uniform one-def no-mutation analysis
145 // and RPO (or recursing when needing the value of a local).
146 let mut optimization_finder
= ConstPropagator
::new(body
, dummy_body
, tcx
);
147 optimization_finder
.visit_body(body
);
149 trace
!("ConstProp done for {:?}", def_id
);
153 struct ConstPropMachine
<'mir
, 'tcx
> {
154 /// The virtual call stack.
155 stack
: Vec
<Frame
<'mir
, 'tcx
>>,
156 /// `OnlyInsideOwnBlock` locals that were written in the current block get erased at the end.
157 written_only_inside_own_block_locals
: FxHashSet
<Local
>,
158 /// Locals that need to be cleared after every block terminates.
159 only_propagate_inside_block_locals
: BitSet
<Local
>,
160 can_const_prop
: IndexVec
<Local
, ConstPropMode
>,
163 impl ConstPropMachine
<'_
, '_
> {
165 only_propagate_inside_block_locals
: BitSet
<Local
>,
166 can_const_prop
: IndexVec
<Local
, ConstPropMode
>,
170 written_only_inside_own_block_locals
: Default
::default(),
171 only_propagate_inside_block_locals
,
177 impl<'mir
, 'tcx
> interpret
::Machine
<'mir
, 'tcx
> for ConstPropMachine
<'mir
, 'tcx
> {
178 compile_time_machine
!(<'mir
, 'tcx
>);
179 const PANIC_ON_ALLOC_FAIL
: bool
= true; // all allocations are small (see `MAX_ALLOC_LIMIT`)
184 _ecx
: &InterpCx
<'mir
, 'tcx
, Self>,
185 _instance
: ty
::InstanceDef
<'tcx
>,
186 ) -> InterpResult
<'tcx
, &'tcx Body
<'tcx
>> {
187 throw_machine_stop_str
!("calling functions isn't supported in ConstProp")
190 fn find_mir_or_eval_fn(
191 _ecx
: &mut InterpCx
<'mir
, 'tcx
, Self>,
192 _instance
: ty
::Instance
<'tcx
>,
194 _args
: &[OpTy
<'tcx
>],
195 _destination
: &PlaceTy
<'tcx
>,
196 _target
: Option
<BasicBlock
>,
197 _unwind
: StackPopUnwind
,
198 ) -> InterpResult
<'tcx
, Option
<(&'mir Body
<'tcx
>, ty
::Instance
<'tcx
>)>> {
203 _ecx
: &mut InterpCx
<'mir
, 'tcx
, Self>,
204 _instance
: ty
::Instance
<'tcx
>,
205 _args
: &[OpTy
<'tcx
>],
206 _destination
: &PlaceTy
<'tcx
>,
207 _target
: Option
<BasicBlock
>,
208 _unwind
: StackPopUnwind
,
209 ) -> InterpResult
<'tcx
> {
210 throw_machine_stop_str
!("calling intrinsics isn't supported in ConstProp")
214 _ecx
: &mut InterpCx
<'mir
, 'tcx
, Self>,
215 _msg
: &rustc_middle
::mir
::AssertMessage
<'tcx
>,
216 _unwind
: Option
<rustc_middle
::mir
::BasicBlock
>,
217 ) -> InterpResult
<'tcx
> {
218 bug
!("panics terminators are not evaluated in ConstProp")
222 _ecx
: &InterpCx
<'mir
, 'tcx
, Self>,
225 _right
: &ImmTy
<'tcx
>,
226 ) -> InterpResult
<'tcx
, (Scalar
, bool
, Ty
<'tcx
>)> {
227 // We can't do this because aliasing of memory can differ between const eval and llvm
228 throw_machine_stop_str
!("pointer arithmetic or comparisons aren't supported in ConstProp")
232 _ecx
: &InterpCx
<'mir
, 'tcx
, Self>,
233 frame
: &Frame
<'mir
, 'tcx
, Self::PointerTag
, Self::FrameExtra
>,
235 ) -> InterpResult
<'tcx
, InterpOperand
<Self::PointerTag
>> {
236 let l
= &frame
.locals
[local
];
238 if l
.value
== LocalValue
::Unallocated
{
239 throw_machine_stop_str
!("tried to access an uninitialized local")
245 fn access_local_mut
<'a
>(
246 ecx
: &'a
mut InterpCx
<'mir
, 'tcx
, Self>,
249 ) -> InterpResult
<'tcx
, Result
<&'a
mut LocalValue
<Self::PointerTag
>, MemPlace
<Self::PointerTag
>>>
251 if ecx
.machine
.can_const_prop
[local
] == ConstPropMode
::NoPropagation
{
252 throw_machine_stop_str
!("tried to write to a local that is marked as not propagatable")
254 if frame
== 0 && ecx
.machine
.only_propagate_inside_block_locals
.contains(local
) {
256 "mutating local {:?} which is restricted to its block. \
257 Will remove it from const-prop after block is finished.",
260 ecx
.machine
.written_only_inside_own_block_locals
.insert(local
);
262 ecx
.machine
.stack
[frame
].locals
[local
].access_mut()
265 fn before_access_global(
269 alloc
: ConstAllocation
<'tcx
, Self::PointerTag
, Self::AllocExtra
>,
270 _static_def_id
: Option
<DefId
>,
272 ) -> InterpResult
<'tcx
> {
274 throw_machine_stop_str
!("can't write to global");
276 // If the static allocation is mutable, then we can't const prop it as its content
277 // might be different at runtime.
278 if alloc
.inner().mutability
== Mutability
::Mut
{
279 throw_machine_stop_str
!("can't access mutable globals in ConstProp");
287 _ecx
: &mut InterpCx
<'mir
, 'tcx
, Self>,
288 _ptr
: Pointer
<AllocId
>,
289 ) -> InterpResult
<'tcx
> {
290 throw_machine_stop_str
!("exposing pointers isn't supported in ConstProp")
295 _ecx
: &mut InterpCx
<'mir
, 'tcx
, Self>,
296 frame
: Frame
<'mir
, 'tcx
>,
297 ) -> InterpResult
<'tcx
, Frame
<'mir
, 'tcx
>> {
303 ecx
: &'a InterpCx
<'mir
, 'tcx
, Self>,
304 ) -> &'a
[Frame
<'mir
, 'tcx
, Self::PointerTag
, Self::FrameExtra
>] {
310 ecx
: &'a
mut InterpCx
<'mir
, 'tcx
, Self>,
311 ) -> &'a
mut Vec
<Frame
<'mir
, 'tcx
, Self::PointerTag
, Self::FrameExtra
>> {
312 &mut ecx
.machine
.stack
316 /// Finds optimization opportunities on the MIR.
317 struct ConstPropagator
<'mir
, 'tcx
> {
318 ecx
: InterpCx
<'mir
, 'tcx
, ConstPropMachine
<'mir
, 'tcx
>>,
320 param_env
: ParamEnv
<'tcx
>,
321 source_scopes
: &'mir IndexVec
<SourceScope
, SourceScopeData
<'tcx
>>,
322 local_decls
: &'mir IndexVec
<Local
, LocalDecl
<'tcx
>>,
323 // Because we have `MutVisitor` we can't obtain the `SourceInfo` from a `Location`. So we store
324 // the last known `SourceInfo` here and just keep revisiting it.
325 source_info
: Option
<SourceInfo
>,
328 impl<'tcx
> LayoutOfHelpers
<'tcx
> for ConstPropagator
<'_
, 'tcx
> {
329 type LayoutOfResult
= Result
<TyAndLayout
<'tcx
>, LayoutError
<'tcx
>>;
332 fn handle_layout_err(&self, err
: LayoutError
<'tcx
>, _
: Span
, _
: Ty
<'tcx
>) -> LayoutError
<'tcx
> {
337 impl HasDataLayout
for ConstPropagator
<'_
, '_
> {
339 fn data_layout(&self) -> &TargetDataLayout
{
340 &self.tcx
.data_layout
344 impl<'tcx
> ty
::layout
::HasTyCtxt
<'tcx
> for ConstPropagator
<'_
, 'tcx
> {
346 fn tcx(&self) -> TyCtxt
<'tcx
> {
351 impl<'tcx
> ty
::layout
::HasParamEnv
<'tcx
> for ConstPropagator
<'_
, 'tcx
> {
353 fn param_env(&self) -> ty
::ParamEnv
<'tcx
> {
358 impl<'mir
, 'tcx
> ConstPropagator
<'mir
, 'tcx
> {
361 dummy_body
: &'mir Body
<'tcx
>,
363 ) -> ConstPropagator
<'mir
, 'tcx
> {
364 let def_id
= body
.source
.def_id();
365 let substs
= &InternalSubsts
::identity_for_item(tcx
, def_id
);
366 let param_env
= tcx
.param_env_reveal_all_normalized(def_id
);
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
);
375 let mut ecx
= InterpCx
::new(
377 tcx
.def_span(def_id
),
379 ConstPropMachine
::new(only_propagate_inside_block_locals
, can_const_prop
),
383 .layout_of(EarlyBinder(body
.return_ty()).subst(tcx
, substs
))
385 // Don't bother allocating memory for large values.
386 .filter(|ret_layout
| ret_layout
.size
< Size
::from_bytes(MAX_ALLOC_LIMIT
))
387 .unwrap_or_else(|| ecx
.layout_of(tcx
.types
.unit
).unwrap());
390 .allocate(ret_layout
, MemoryKind
::Stack
)
391 .expect("couldn't perform small allocation")
394 ecx
.push_stack_frame(
395 Instance
::new(def_id
, substs
),
398 StackPopCleanup
::Root { cleanup: false }
,
400 .expect("failed to push initial stack frame");
406 source_scopes
: &dummy_body
.source_scopes
,
407 local_decls
: &dummy_body
.local_decls
,
412 fn get_const(&self, place
: Place
<'tcx
>) -> Option
<OpTy
<'tcx
>> {
413 let op
= match self.ecx
.eval_place_to_op(place
, None
) {
416 trace
!("get_const failed: {}", e
);
421 // Try to read the local as an immediate so that if it is representable as a scalar, we can
422 // handle it as such, but otherwise, just return the value as is.
423 Some(match self.ecx
.read_immediate_raw(&op
, /*force*/ false) {
424 Ok(Ok(imm
)) => imm
.into(),
429 /// Remove `local` from the pool of `Locals`. Allows writing to them,
430 /// but not reading from them anymore.
431 fn remove_const(ecx
: &mut InterpCx
<'mir
, 'tcx
, ConstPropMachine
<'mir
, 'tcx
>>, local
: Local
) {
432 ecx
.frame_mut().locals
[local
] =
433 LocalState { value: LocalValue::Unallocated, layout: Cell::new(None) }
;
436 fn lint_root(&self, source_info
: SourceInfo
) -> Option
<HirId
> {
437 source_info
.scope
.lint_root(self.source_scopes
)
440 fn use_ecx
<F
, T
>(&mut self, source_info
: SourceInfo
, f
: F
) -> Option
<T
>
442 F
: FnOnce(&mut Self) -> InterpResult
<'tcx
, T
>,
444 // Overwrite the PC -- whatever the interpreter does to it does not make any sense anyway.
445 self.ecx
.frame_mut().loc
= Err(source_info
.span
);
447 Ok(val
) => Some(val
),
449 trace
!("InterpCx operation failed: {:?}", error
);
450 // Some errors shouldn't come up because creating them causes
451 // an allocation, which we should avoid. When that happens,
452 // dedicated error variants should be introduced instead.
454 !error
.kind().formatted_string(),
455 "const-prop encountered formatting error: {}",
463 /// Returns the value, if any, of evaluating `c`.
464 fn eval_constant(&mut self, c
: &Constant
<'tcx
>, source_info
: SourceInfo
) -> Option
<OpTy
<'tcx
>> {
465 // FIXME we need to revisit this for #67176
470 match self.ecx
.mir_const_to_op(&c
.literal
, None
) {
473 let tcx
= self.ecx
.tcx
.at(c
.span
);
474 let err
= ConstEvalErr
::new(&self.ecx
, error
, Some(c
.span
));
475 if let Some(lint_root
) = self.lint_root(source_info
) {
476 let lint_only
= match c
.literal
{
477 ConstantKind
::Ty(ct
) => match ct
.kind() {
478 // Promoteds must lint and not error as the user didn't ask for them
479 ConstKind
::Unevaluated(ty
::Unevaluated
{
484 // Out of backwards compatibility we cannot report hard errors in unused
485 // generic functions using associated constants of the generic parameters.
486 _
=> c
.literal
.needs_subst(),
488 ConstantKind
::Val(_
, ty
) => ty
.needs_subst(),
491 // Out of backwards compatibility we cannot report hard errors in unused
492 // generic functions using associated constants of the generic parameters.
493 err
.report_as_lint(tcx
, "erroneous constant used", lint_root
, Some(c
.span
));
495 err
.report_as_error(tcx
, "erroneous constant used");
498 err
.report_as_error(tcx
, "erroneous constant used");
505 /// Returns the value, if any, of evaluating `place`.
506 fn eval_place(&mut self, place
: Place
<'tcx
>, source_info
: SourceInfo
) -> Option
<OpTy
<'tcx
>> {
507 trace
!("eval_place(place={:?})", place
);
508 self.use_ecx(source_info
, |this
| this
.ecx
.eval_place_to_op(place
, None
))
511 /// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant`
512 /// or `eval_place`, depending on the variant of `Operand` used.
513 fn eval_operand(&mut self, op
: &Operand
<'tcx
>, source_info
: SourceInfo
) -> Option
<OpTy
<'tcx
>> {
515 Operand
::Constant(ref c
) => self.eval_constant(c
, source_info
),
516 Operand
::Move(place
) | Operand
::Copy(place
) => self.eval_place(place
, source_info
),
520 fn report_assert_as_lint(
522 lint
: &'
static lint
::Lint
,
523 source_info
: SourceInfo
,
524 message
: &'
static str,
525 panic
: AssertKind
<impl std
::fmt
::Debug
>,
527 if let Some(lint_root
) = self.lint_root(source_info
) {
528 self.tcx
.struct_span_lint_hir(lint
, lint_root
, source_info
.span
, |lint
| {
529 let mut err
= lint
.build(message
);
530 err
.span_label(source_info
.span
, format
!("{:?}", panic
));
540 source_info
: SourceInfo
,
542 if let (val
, true) = self.use_ecx(source_info
, |this
| {
543 let val
= this
.ecx
.read_immediate(&this
.ecx
.eval_operand(arg
, None
)?
)?
;
544 let (_res
, overflow
, _ty
) = this
.ecx
.overflowing_unary_op(op
, &val
)?
;
547 // `AssertKind` only has an `OverflowNeg` variant, so make sure that is
548 // appropriate to use.
549 assert_eq
!(op
, UnOp
::Neg
, "Neg is the only UnOp that can overflow");
550 self.report_assert_as_lint(
551 lint
::builtin
::ARITHMETIC_OVERFLOW
,
553 "this arithmetic operation will overflow",
554 AssertKind
::OverflowNeg(val
.to_const_int()),
565 left
: &Operand
<'tcx
>,
566 right
: &Operand
<'tcx
>,
567 source_info
: SourceInfo
,
569 let r
= self.use_ecx(source_info
, |this
| {
570 this
.ecx
.read_immediate(&this
.ecx
.eval_operand(right
, None
)?
)
572 let l
= self.use_ecx(source_info
, |this
| {
573 this
.ecx
.read_immediate(&this
.ecx
.eval_operand(left
, None
)?
)
575 // Check for exceeding shifts *even if* we cannot evaluate the LHS.
576 if op
== BinOp
::Shr
|| op
== BinOp
::Shl
{
578 // We need the type of the LHS. We cannot use `place_layout` as that is the type
579 // of the result, which for checked binops is not the same!
580 let left_ty
= left
.ty(self.local_decls
, self.tcx
);
581 let left_size
= self.ecx
.layout_of(left_ty
).ok()?
.size
;
582 let right_size
= r
.layout
.size
;
583 let r_bits
= r
.to_scalar().ok();
584 let r_bits
= r_bits
.and_then(|r
| r
.to_bits(right_size
).ok());
585 if r_bits
.map_or(false, |b
| b
>= left_size
.bits() as u128
) {
586 debug
!("check_binary_op: reporting assert for {:?}", source_info
);
587 self.report_assert_as_lint(
588 lint
::builtin
::ARITHMETIC_OVERFLOW
,
590 "this arithmetic operation will overflow",
591 AssertKind
::Overflow(
594 Some(l
) => l
.to_const_int(),
595 // Invent a dummy value, the diagnostic ignores it anyway
596 None
=> ConstInt
::new(
597 ScalarInt
::try_from_uint(1_u8, left_size
).unwrap(),
599 left_ty
.is_ptr_sized_integral(),
609 if let (Some(l
), Some(r
)) = (&l
, &r
) {
610 // The remaining operators are handled through `overflowing_binary_op`.
611 if self.use_ecx(source_info
, |this
| {
612 let (_res
, overflow
, _ty
) = this
.ecx
.overflowing_binary_op(op
, l
, r
)?
;
615 self.report_assert_as_lint(
616 lint
::builtin
::ARITHMETIC_OVERFLOW
,
618 "this arithmetic operation will overflow",
619 AssertKind
::Overflow(op
, l
.to_const_int(), r
.to_const_int()),
629 rvalue
: &Rvalue
<'tcx
>,
630 source_info
: SourceInfo
,
633 // Perform any special handling for specific Rvalue types.
634 // Generally, checks here fall into one of two categories:
635 // 1. Additional checking to provide useful lints to the user
636 // - In this case, we will do some validation and then fall through to the
637 // end of the function which evals the assignment.
638 // 2. Working around bugs in other parts of the compiler
639 // - In this case, we'll return `None` from this function to stop evaluation.
641 // Additional checking: give lints to the user if an overflow would occur.
642 // We do this here and not in the `Assert` terminator as that terminator is
643 // only sometimes emitted (overflow checks can be disabled), but we want to always
645 Rvalue
::UnaryOp(op
, arg
) => {
646 trace
!("checking UnaryOp(op = {:?}, arg = {:?})", op
, arg
);
647 self.check_unary_op(*op
, arg
, source_info
)?
;
649 Rvalue
::BinaryOp(op
, box (left
, right
)) => {
650 trace
!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op
, left
, right
);
651 self.check_binary_op(*op
, left
, right
, source_info
)?
;
653 Rvalue
::CheckedBinaryOp(op
, box (left
, right
)) => {
655 "checking CheckedBinaryOp(op = {:?}, left = {:?}, right = {:?})",
660 self.check_binary_op(*op
, left
, right
, source_info
)?
;
663 // Do not try creating references (#67862)
664 Rvalue
::AddressOf(_
, place
) | Rvalue
::Ref(_
, _
, place
) => {
665 trace
!("skipping AddressOf | Ref for {:?}", place
);
667 // This may be creating mutable references or immutable references to cells.
668 // If that happens, the pointed to value could be mutated via that reference.
669 // Since we aren't tracking references, the const propagator loses track of what
670 // value the local has right now.
671 // Thus, all locals that have their reference taken
672 // must not take part in propagation.
673 Self::remove_const(&mut self.ecx
, place
.local
);
677 Rvalue
::ThreadLocalRef(def_id
) => {
678 trace
!("skipping ThreadLocalRef({:?})", def_id
);
683 // There's no other checking to do at this time.
684 Rvalue
::Aggregate(..)
689 | Rvalue
::ShallowInitBox(..)
690 | Rvalue
::Discriminant(..)
691 | Rvalue
::NullaryOp(..) => {}
694 // FIXME we need to revisit this for #67176
695 if rvalue
.needs_subst() {
699 self.use_ecx(source_info
, |this
| this
.ecx
.eval_rvalue_into_place(rvalue
, place
))
703 /// The mode that `ConstProp` is allowed to run in for a given `Local`.
704 #[derive(Clone, Copy, Debug, PartialEq)]
706 /// The `Local` can be propagated into and reads of this `Local` can also be propagated.
708 /// The `Local` can only be propagated into and from its own block.
710 /// The `Local` can be propagated into but reads cannot be propagated.
712 /// The `Local` cannot be part of propagation at all. Any statement
713 /// referencing it either for reading or writing will not get propagated.
717 struct CanConstProp
{
718 can_const_prop
: IndexVec
<Local
, ConstPropMode
>,
719 // False at the beginning. Once set, no more assignments are allowed to that local.
720 found_assignment
: BitSet
<Local
>,
721 // Cache of locals' information
722 local_kinds
: IndexVec
<Local
, LocalKind
>,
726 /// Returns true if `local` can be propagated
729 param_env
: ParamEnv
<'tcx
>,
731 ) -> IndexVec
<Local
, ConstPropMode
> {
732 let mut cpv
= CanConstProp
{
733 can_const_prop
: IndexVec
::from_elem(ConstPropMode
::FullConstProp
, &body
.local_decls
),
734 found_assignment
: BitSet
::new_empty(body
.local_decls
.len()),
735 local_kinds
: IndexVec
::from_fn_n(
736 |local
| body
.local_kind(local
),
737 body
.local_decls
.len(),
740 for (local
, val
) in cpv
.can_const_prop
.iter_enumerated_mut() {
741 let ty
= body
.local_decls
[local
].ty
;
742 match tcx
.layout_of(param_env
.and(ty
)) {
743 Ok(layout
) if layout
.size
< Size
::from_bytes(MAX_ALLOC_LIMIT
) => {}
744 // Either the layout fails to compute, then we can't use this local anyway
745 // or the local is too large, then we don't want to.
747 *val
= ConstPropMode
::NoPropagation
;
751 // Cannot use args at all
752 // Cannot use locals because if x < y { y - x } else { x - y } would
754 // FIXME(oli-obk): lint variables until they are used in a condition
755 // FIXME(oli-obk): lint if return value is constant
756 if cpv
.local_kinds
[local
] == LocalKind
::Arg
{
757 *val
= ConstPropMode
::OnlyPropagateInto
;
759 "local {:?} can't be const propagated because it's a function argument",
762 } else if cpv
.local_kinds
[local
] == LocalKind
::Var
{
763 *val
= ConstPropMode
::OnlyInsideOwnBlock
;
765 "local {:?} will only be propagated inside its block, because it's a user variable",
770 cpv
.visit_body(&body
);
775 impl Visitor
<'_
> for CanConstProp
{
776 fn visit_local(&mut self, &local
: &Local
, context
: PlaceContext
, _
: Location
) {
777 use rustc_middle
::mir
::visit
::PlaceContext
::*;
779 // Projections are fine, because `&mut foo.x` will be caught by
780 // `MutatingUseContext::Borrow` elsewhere.
781 MutatingUse(MutatingUseContext
::Projection
)
782 // These are just stores, where the storing is not propagatable, but there may be later
783 // mutations of the same local via `Store`
784 | MutatingUse(MutatingUseContext
::Call
)
785 | MutatingUse(MutatingUseContext
::AsmOutput
)
786 | MutatingUse(MutatingUseContext
::Deinit
)
787 // Actual store that can possibly even propagate a value
788 | MutatingUse(MutatingUseContext
::SetDiscriminant
)
789 | MutatingUse(MutatingUseContext
::Store
) => {
790 if !self.found_assignment
.insert(local
) {
791 match &mut self.can_const_prop
[local
] {
792 // If the local can only get propagated in its own block, then we don't have
793 // to worry about multiple assignments, as we'll nuke the const state at the
794 // end of the block anyway, and inside the block we overwrite previous
795 // states as applicable.
796 ConstPropMode
::OnlyInsideOwnBlock
=> {}
797 ConstPropMode
::NoPropagation
=> {}
798 ConstPropMode
::OnlyPropagateInto
=> {}
799 other @ ConstPropMode
::FullConstProp
=> {
801 "local {:?} can't be propagated because of multiple assignments. Previous state: {:?}",
804 *other
= ConstPropMode
::OnlyInsideOwnBlock
;
809 // Reading constants is allowed an arbitrary number of times
810 NonMutatingUse(NonMutatingUseContext
::Copy
)
811 | NonMutatingUse(NonMutatingUseContext
::Move
)
812 | NonMutatingUse(NonMutatingUseContext
::Inspect
)
813 | NonMutatingUse(NonMutatingUseContext
::Projection
)
816 // These could be propagated with a smarter analysis or just some careful thinking about
817 // whether they'd be fine right now.
818 MutatingUse(MutatingUseContext
::Yield
)
819 | MutatingUse(MutatingUseContext
::Drop
)
820 | MutatingUse(MutatingUseContext
::Retag
)
821 // These can't ever be propagated under any scheme, as we can't reason about indirect
823 | NonMutatingUse(NonMutatingUseContext
::SharedBorrow
)
824 | NonMutatingUse(NonMutatingUseContext
::ShallowBorrow
)
825 | NonMutatingUse(NonMutatingUseContext
::UniqueBorrow
)
826 | NonMutatingUse(NonMutatingUseContext
::AddressOf
)
827 | MutatingUse(MutatingUseContext
::Borrow
)
828 | MutatingUse(MutatingUseContext
::AddressOf
) => {
829 trace
!("local {:?} can't be propagaged because it's used: {:?}", local
, context
);
830 self.can_const_prop
[local
] = ConstPropMode
::NoPropagation
;
836 impl<'tcx
> Visitor
<'tcx
> for ConstPropagator
<'_
, 'tcx
> {
837 fn visit_body(&mut self, body
: &Body
<'tcx
>) {
838 for (bb
, data
) in body
.basic_blocks().iter_enumerated() {
839 self.visit_basic_block_data(bb
, data
);
843 fn visit_operand(&mut self, operand
: &Operand
<'tcx
>, location
: Location
) {
844 self.super_operand(operand
, location
);
847 fn visit_constant(&mut self, constant
: &Constant
<'tcx
>, location
: Location
) {
848 trace
!("visit_constant: {:?}", constant
);
849 self.super_constant(constant
, location
);
850 self.eval_constant(constant
, self.source_info
.unwrap());
853 fn visit_statement(&mut self, statement
: &Statement
<'tcx
>, location
: Location
) {
854 trace
!("visit_statement: {:?}", statement
);
855 let source_info
= statement
.source_info
;
856 self.source_info
= Some(source_info
);
857 if let StatementKind
::Assign(box (place
, ref rval
)) = statement
.kind
{
858 let can_const_prop
= self.ecx
.machine
.can_const_prop
[place
.local
];
859 if let Some(()) = self.const_prop(rval
, source_info
, place
) {
860 match can_const_prop
{
861 ConstPropMode
::OnlyInsideOwnBlock
=> {
863 "found local restricted to its block. \
864 Will remove it from const-prop after block is finished. Local: {:?}",
868 ConstPropMode
::OnlyPropagateInto
| ConstPropMode
::NoPropagation
=> {
869 trace
!("can't propagate into {:?}", place
);
870 if place
.local
!= RETURN_PLACE
{
871 Self::remove_const(&mut self.ecx
, place
.local
);
874 ConstPropMode
::FullConstProp
=> {}
877 // Const prop failed, so erase the destination, ensuring that whatever happens
878 // from here on, does not know about the previous value.
879 // This is important in case we have
882 // x = SOME_MUTABLE_STATIC;
883 // // x must now be uninit
885 // FIXME: we overzealously erase the entire local, because that's easier to
888 "propagation into {:?} failed.
889 Nuking the entire site from orbit, it's the only way to be sure",
892 Self::remove_const(&mut self.ecx
, place
.local
);
895 match statement
.kind
{
896 StatementKind
::SetDiscriminant { ref place, .. }
=> {
897 match self.ecx
.machine
.can_const_prop
[place
.local
] {
898 ConstPropMode
::FullConstProp
| ConstPropMode
::OnlyInsideOwnBlock
=> {
900 .use_ecx(source_info
, |this
| this
.ecx
.statement(statement
))
903 trace
!("propped discriminant into {:?}", place
);
905 Self::remove_const(&mut self.ecx
, place
.local
);
908 ConstPropMode
::OnlyPropagateInto
| ConstPropMode
::NoPropagation
=> {
909 Self::remove_const(&mut self.ecx
, place
.local
);
913 StatementKind
::StorageLive(local
) | StatementKind
::StorageDead(local
) => {
914 let frame
= self.ecx
.frame_mut();
915 frame
.locals
[local
].value
=
916 if let StatementKind
::StorageLive(_
) = statement
.kind
{
917 LocalValue
::Unallocated
926 self.super_statement(statement
, location
);
929 fn visit_terminator(&mut self, terminator
: &Terminator
<'tcx
>, location
: Location
) {
930 let source_info
= terminator
.source_info
;
931 self.source_info
= Some(source_info
);
932 self.super_terminator(terminator
, location
);
933 match &terminator
.kind
{
934 TerminatorKind
::Assert { expected, ref msg, ref cond, .. }
=> {
935 if let Some(ref value
) = self.eval_operand(&cond
, source_info
) {
936 trace
!("assertion on {:?} should be {:?}", value
, expected
);
937 let expected
= ScalarMaybeUninit
::from(Scalar
::from_bool(*expected
));
938 let value_const
= self.ecx
.read_scalar(&value
).unwrap();
939 if expected
!= value_const
{
944 impl<T
: std
::fmt
::Debug
> std
::fmt
::Debug
for DbgVal
<T
> {
945 fn fmt(&self, fmt
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
947 Self::Val(val
) => val
.fmt(fmt
),
948 Self::Underscore
=> fmt
.write_str("_"),
952 let mut eval_to_int
= |op
| {
953 // This can be `None` if the lhs wasn't const propagated and we just
954 // triggered the assert on the value of the rhs.
955 self.eval_operand(op
, source_info
).map_or(DbgVal
::Underscore
, |op
| {
956 DbgVal
::Val(self.ecx
.read_immediate(&op
).unwrap().to_const_int())
959 let msg
= match msg
{
960 AssertKind
::DivisionByZero(op
) => {
961 Some(AssertKind
::DivisionByZero(eval_to_int(op
)))
963 AssertKind
::RemainderByZero(op
) => {
964 Some(AssertKind
::RemainderByZero(eval_to_int(op
)))
966 AssertKind
::Overflow(bin_op @
(BinOp
::Div
| BinOp
::Rem
), op1
, op2
) => {
967 // Division overflow is *UB* in the MIR, and different than the
968 // other overflow checks.
969 Some(AssertKind
::Overflow(
975 AssertKind
::BoundsCheck { ref len, ref index }
=> {
976 let len
= eval_to_int(len
);
977 let index
= eval_to_int(index
);
978 Some(AssertKind
::BoundsCheck { len, index }
)
980 // Remaining overflow errors are already covered by checks on the binary operators.
981 AssertKind
::Overflow(..) | AssertKind
::OverflowNeg(_
) => None
,
982 // Need proper const propagator for these.
985 // Poison all places this operand references so that further code
986 // doesn't use the invalid value
988 Operand
::Move(ref place
) | Operand
::Copy(ref place
) => {
989 Self::remove_const(&mut self.ecx
, place
.local
);
991 Operand
::Constant(_
) => {}
993 if let Some(msg
) = msg
{
994 self.report_assert_as_lint(
995 lint
::builtin
::UNCONDITIONAL_PANIC
,
997 "this operation will panic at runtime",
1004 // None of these have Operands to const-propagate.
1005 TerminatorKind
::Goto { .. }
1006 | TerminatorKind
::Resume
1007 | TerminatorKind
::Abort
1008 | TerminatorKind
::Return
1009 | TerminatorKind
::Unreachable
1010 | TerminatorKind
::Drop { .. }
1011 | TerminatorKind
::DropAndReplace { .. }
1012 | TerminatorKind
::Yield { .. }
1013 | TerminatorKind
::GeneratorDrop
1014 | TerminatorKind
::FalseEdge { .. }
1015 | TerminatorKind
::FalseUnwind { .. }
1016 | TerminatorKind
::SwitchInt { .. }
1017 | TerminatorKind
::Call { .. }
1018 | TerminatorKind
::InlineAsm { .. }
=> {}
1021 // We remove all Locals which are restricted in propagation to their containing blocks and
1022 // which were modified in the current block.
1023 // Take it out of the ecx so we can get a mutable reference to the ecx for `remove_const`.
1024 let mut locals
= std
::mem
::take(&mut self.ecx
.machine
.written_only_inside_own_block_locals
);
1025 for &local
in locals
.iter() {
1026 Self::remove_const(&mut self.ecx
, local
);
1029 // Put it back so we reuse the heap of the storage
1030 self.ecx
.machine
.written_only_inside_own_block_locals
= locals
;
1031 if cfg
!(debug_assertions
) {
1032 // Ensure we are correctly erasing locals with the non-debug-assert logic.
1033 for local
in self.ecx
.machine
.only_propagate_inside_block_locals
.iter() {
1035 self.get_const(local
.into()).is_none()
1037 .layout_of(self.local_decls
[local
].ty
)
1038 .map_or(true, |layout
| layout
.is_zst())