1 //! Propagates constants for early reporting of statically known
7 use rustc
::hir
::def
::DefKind
;
8 use rustc
::hir
::def_id
::DefId
;
10 AggregateKind
, Constant
, Location
, Place
, PlaceBase
, Body
, BodyAndCache
, Operand
, Local
, UnOp
,
11 Rvalue
, StatementKind
, Statement
, LocalKind
, TerminatorKind
, Terminator
, ClearCrossCrate
,
12 SourceInfo
, BinOp
, SourceScope
, SourceScopeData
, LocalDecl
, BasicBlock
, ReadOnlyBodyAndCache
,
13 read_only
, RETURN_PLACE
15 use rustc
::mir
::visit
::{
16 Visitor
, PlaceContext
, MutatingUseContext
, MutVisitor
, NonMutatingUseContext
,
18 use rustc
::mir
::interpret
::{Scalar, InterpResult, PanicInfo}
;
19 use rustc
::ty
::{self, Instance, ParamEnv, Ty, TyCtxt}
;
20 use syntax
::ast
::Mutability
;
21 use syntax_pos
::{Span, DUMMY_SP}
;
22 use rustc
::ty
::subst
::InternalSubsts
;
23 use rustc_data_structures
::fx
::FxHashMap
;
24 use rustc_index
::vec
::IndexVec
;
25 use rustc
::ty
::layout
::{
26 LayoutOf
, TyLayout
, LayoutError
, HasTyCtxt
, TargetDataLayout
, HasDataLayout
, Size
,
29 use crate::rustc
::ty
::subst
::Subst
;
30 use crate::interpret
::{
31 self, InterpCx
, ScalarMaybeUndef
, Immediate
, OpTy
,
32 StackPopCleanup
, LocalValue
, LocalState
, AllocId
, Frame
,
33 Allocation
, MemoryKind
, ImmTy
, Pointer
, Memory
, PlaceTy
,
34 Operand
as InterpOperand
, intern_const_alloc_recursive
,
36 use crate::const_eval
::error_to_const_error
;
37 use crate::transform
::{MirPass, MirSource}
;
39 /// The maximum number of bytes that we'll allocate space for a return value.
40 const MAX_ALLOC_LIMIT
: u64 = 1024;
44 impl<'tcx
> MirPass
<'tcx
> for ConstProp
{
46 &self, tcx
: TyCtxt
<'tcx
>, source
: MirSource
<'tcx
>, body
: &mut BodyAndCache
<'tcx
>
48 // will be evaluated by miri and produce its errors there
49 if source
.promoted
.is_some() {
53 use rustc
::hir
::map
::blocks
::FnLikeNode
;
54 let hir_id
= tcx
.hir().as_local_hir_id(source
.def_id())
55 .expect("Non-local call to local provider is_const_fn");
57 let is_fn_like
= FnLikeNode
::from_node(tcx
.hir().get(hir_id
)).is_some();
58 let is_assoc_const
= match tcx
.def_kind(source
.def_id()) {
59 Some(DefKind
::AssocConst
) => true,
63 // Only run const prop on functions, methods, closures and associated constants
64 if !is_fn_like
&& !is_assoc_const
{
65 // skip anon_const/statics/consts because they'll be evaluated by miri anyway
66 trace
!("ConstProp skipped for {:?}", source
.def_id());
70 let is_generator
= tcx
.type_of(source
.def_id()).is_generator();
71 // FIXME(welseywiser) const prop doesn't work on generators because of query cycles
72 // computing their layout.
74 trace
!("ConstProp skipped for generator {:?}", source
.def_id());
78 trace
!("ConstProp starting for {:?}", source
.def_id());
82 body
.basic_blocks().clone(),
83 body
.source_scopes
.clone(),
84 body
.local_decls
.clone(),
88 tcx
.def_span(source
.def_id()),
93 // FIXME(oli-obk, eddyb) Optimize locals (or even local paths) to hold
94 // constants, instead of just checking for const-folding succeeding.
95 // That would require an uniform one-def no-mutation analysis
96 // and RPO (or recursing when needing the value of a local).
97 let mut optimization_finder
= ConstPropagator
::new(
103 optimization_finder
.visit_body(body
);
105 trace
!("ConstProp done for {:?}", source
.def_id());
109 struct ConstPropMachine
;
111 impl<'mir
, 'tcx
> interpret
::Machine
<'mir
, 'tcx
> for ConstPropMachine
{
112 type MemoryKinds
= !;
113 type PointerTag
= ();
116 type FrameExtra
= ();
117 type MemoryExtra
= ();
118 type AllocExtra
= ();
120 type MemoryMap
= FxHashMap
<AllocId
, (MemoryKind
<!>, Allocation
)>;
122 const STATIC_KIND
: Option
<!> = None
;
124 const CHECK_ALIGN
: bool
= false;
127 fn enforce_validity(_ecx
: &InterpCx
<'mir
, 'tcx
, Self>) -> bool
{
131 fn find_mir_or_eval_fn(
132 _ecx
: &mut InterpCx
<'mir
, 'tcx
, Self>,
133 _instance
: ty
::Instance
<'tcx
>,
134 _args
: &[OpTy
<'tcx
>],
135 _ret
: Option
<(PlaceTy
<'tcx
>, BasicBlock
)>,
136 _unwind
: Option
<BasicBlock
>,
137 ) -> InterpResult
<'tcx
, Option
<&'mir Body
<'tcx
>>> {
142 _ecx
: &mut InterpCx
<'mir
, 'tcx
, Self>,
144 _args
: &[OpTy
<'tcx
>],
145 _ret
: Option
<(PlaceTy
<'tcx
>, BasicBlock
)>,
146 _unwind
: Option
<BasicBlock
>
147 ) -> InterpResult
<'tcx
> {
152 _ecx
: &mut InterpCx
<'mir
, 'tcx
, Self>,
154 _instance
: ty
::Instance
<'tcx
>,
155 _args
: &[OpTy
<'tcx
>],
156 _ret
: Option
<(PlaceTy
<'tcx
>, BasicBlock
)>,
157 _unwind
: Option
<BasicBlock
>
158 ) -> InterpResult
<'tcx
> {
159 throw_unsup
!(ConstPropUnsupported("calling intrinsics isn't supported in ConstProp"));
163 _ecx
: &mut InterpCx
<'mir
, 'tcx
, Self>,
165 _msg
: &rustc
::mir
::interpret
::AssertMessage
<'tcx
>,
166 _unwind
: Option
<rustc
::mir
::BasicBlock
>,
167 ) -> InterpResult
<'tcx
> {
168 bug
!("panics terminators are not evaluated in ConstProp");
172 _mem
: &Memory
<'mir
, 'tcx
, Self>,
174 ) -> InterpResult
<'tcx
, u64> {
175 throw_unsup
!(ConstPropUnsupported("ptr-to-int casts aren't supported in ConstProp"));
179 _ecx
: &InterpCx
<'mir
, 'tcx
, Self>,
183 ) -> InterpResult
<'tcx
, (Scalar
, bool
, Ty
<'tcx
>)> {
184 // We can't do this because aliasing of memory can differ between const eval and llvm
185 throw_unsup
!(ConstPropUnsupported("pointer arithmetic or comparisons aren't supported \
189 fn find_foreign_static(
192 ) -> InterpResult
<'tcx
, Cow
<'tcx
, Allocation
<Self::PointerTag
>>> {
193 throw_unsup
!(ReadForeignStatic
)
197 fn init_allocation_extra
<'b
>(
200 alloc
: Cow
<'b
, Allocation
>,
201 _kind
: Option
<MemoryKind
<!>>,
202 ) -> (Cow
<'b
, Allocation
<Self::PointerTag
>>, Self::PointerTag
) {
203 // We do not use a tag so we can just cheaply forward the allocation
208 fn tag_static_base_pointer(
211 ) -> Self::PointerTag
{
216 _ecx
: &mut InterpCx
<'mir
, 'tcx
, Self>,
217 _dest
: PlaceTy
<'tcx
>,
218 ) -> InterpResult
<'tcx
> {
219 throw_unsup
!(ConstPropUnsupported("can't const prop `box` keyword"));
223 _ecx
: &InterpCx
<'mir
, 'tcx
, Self>,
224 frame
: &Frame
<'mir
, 'tcx
, Self::PointerTag
, Self::FrameExtra
>,
226 ) -> InterpResult
<'tcx
, InterpOperand
<Self::PointerTag
>> {
227 let l
= &frame
.locals
[local
];
229 if l
.value
== LocalValue
::Uninitialized
{
230 throw_unsup
!(ConstPropUnsupported("tried to access an uninitialized local"));
236 fn before_access_static(
237 allocation
: &Allocation
<Self::PointerTag
, Self::AllocExtra
>,
238 ) -> InterpResult
<'tcx
> {
239 // if the static allocation is mutable or if it has relocations (it may be legal to mutate
240 // the memory behind that in the future), then we can't const prop it
241 if allocation
.mutability
== Mutability
::Mutable
|| allocation
.relocations().len() > 0 {
242 throw_unsup
!(ConstPropUnsupported("can't eval mutable statics in ConstProp"));
248 fn before_terminator(_ecx
: &mut InterpCx
<'mir
, 'tcx
, Self>) -> InterpResult
<'tcx
> {
253 fn stack_push(_ecx
: &mut InterpCx
<'mir
, 'tcx
, Self>) -> InterpResult
<'tcx
> {
258 type Const
<'tcx
> = OpTy
<'tcx
>;
260 /// Finds optimization opportunities on the MIR.
261 struct ConstPropagator
<'mir
, 'tcx
> {
262 ecx
: InterpCx
<'mir
, 'tcx
, ConstPropMachine
>,
264 source
: MirSource
<'tcx
>,
265 can_const_prop
: IndexVec
<Local
, bool
>,
266 param_env
: ParamEnv
<'tcx
>,
267 // FIXME(eddyb) avoid cloning these two fields more than once,
268 // by accessing them through `ecx` instead.
269 source_scopes
: IndexVec
<SourceScope
, SourceScopeData
>,
270 local_decls
: IndexVec
<Local
, LocalDecl
<'tcx
>>,
271 ret
: Option
<OpTy
<'tcx
, ()>>,
274 impl<'mir
, 'tcx
> LayoutOf
for ConstPropagator
<'mir
, 'tcx
> {
276 type TyLayout
= Result
<TyLayout
<'tcx
>, LayoutError
<'tcx
>>;
278 fn layout_of(&self, ty
: Ty
<'tcx
>) -> Self::TyLayout
{
279 self.tcx
.layout_of(self.param_env
.and(ty
))
283 impl<'mir
, 'tcx
> HasDataLayout
for ConstPropagator
<'mir
, 'tcx
> {
285 fn data_layout(&self) -> &TargetDataLayout
{
286 &self.tcx
.data_layout
290 impl<'mir
, 'tcx
> HasTyCtxt
<'tcx
> for ConstPropagator
<'mir
, 'tcx
> {
292 fn tcx(&self) -> TyCtxt
<'tcx
> {
297 impl<'mir
, 'tcx
> ConstPropagator
<'mir
, 'tcx
> {
299 body
: ReadOnlyBodyAndCache
<'_
, 'tcx
>,
300 dummy_body
: &'mir Body
<'tcx
>,
302 source
: MirSource
<'tcx
>,
303 ) -> ConstPropagator
<'mir
, 'tcx
> {
304 let def_id
= source
.def_id();
305 let param_env
= tcx
.param_env(def_id
);
306 let span
= tcx
.def_span(def_id
);
307 let mut ecx
= InterpCx
::new(tcx
.at(span
), param_env
, ConstPropMachine
, ());
308 let can_const_prop
= CanConstProp
::check(body
);
310 let substs
= &InternalSubsts
::identity_for_item(tcx
, def_id
);
314 .layout_of(body
.return_ty().subst(tcx
, substs
))
316 // Don't bother allocating memory for ZST types which have no values
317 // or for large values.
318 .filter(|ret_layout
| !ret_layout
.is_zst() &&
319 ret_layout
.size
< Size
::from_bytes(MAX_ALLOC_LIMIT
))
320 .map(|ret_layout
| ecx
.allocate(ret_layout
, MemoryKind
::Stack
));
322 ecx
.push_stack_frame(
323 Instance
::new(def_id
, substs
),
327 StackPopCleanup
::None
{
330 ).expect("failed to push initial stack frame");
338 // FIXME(eddyb) avoid cloning these two fields more than once,
339 // by accessing them through `ecx` instead.
340 source_scopes
: body
.source_scopes
.clone(),
341 //FIXME(wesleywiser) we can't steal this because `Visitor::super_visit_body()` needs it
342 local_decls
: body
.local_decls
.clone(),
343 ret
: ret
.map(Into
::into
),
347 fn get_const(&self, local
: Local
) -> Option
<Const
<'tcx
>> {
348 if local
== RETURN_PLACE
{
349 // Try to read the return place as an immediate so that if it is representable as a
350 // scalar, we can handle it as such, but otherwise, just return the value as is.
351 return match self.ret
.map(|ret
| self.ecx
.try_read_immediate(ret
)) {
352 Some(Ok(Ok(imm
))) => Some(imm
.into()),
357 self.ecx
.access_local(self.ecx
.frame(), local
, None
).ok()
360 fn remove_const(&mut self, local
: Local
) {
361 self.ecx
.frame_mut().locals
[local
] = LocalState
{
362 value
: LocalValue
::Uninitialized
,
363 layout
: Cell
::new(None
),
369 source_info
: SourceInfo
,
373 F
: FnOnce(&mut Self) -> InterpResult
<'tcx
, T
>,
375 self.ecx
.tcx
.span
= source_info
.span
;
376 // FIXME(eddyb) move this to the `Panic(_)` error case, so that
377 // `f(self)` is always called, and that the only difference when the
378 // scope's `local_data` is missing, is that the lint isn't emitted.
379 let lint_root
= match &self.source_scopes
[source_info
.scope
].local_data
{
380 ClearCrossCrate
::Set(data
) => data
.lint_root
,
381 ClearCrossCrate
::Clear
=> return None
,
383 let r
= match f(self) {
384 Ok(val
) => Some(val
),
386 use rustc
::mir
::interpret
::{
388 UndefinedBehaviorInfo
,
392 MachineStop(_
) => bug
!("ConstProp does not stop"),
394 // Some error shouldn't come up because creating them causes
395 // an allocation, which we should avoid. When that happens,
396 // dedicated error variants should be introduced instead.
397 // Only test this in debug builds though to avoid disruptions.
398 Unsupported(UnsupportedOpInfo
::Unsupported(_
))
399 | Unsupported(UnsupportedOpInfo
::ValidationFailure(_
))
400 | UndefinedBehavior(UndefinedBehaviorInfo
::Ub(_
))
401 | UndefinedBehavior(UndefinedBehaviorInfo
::UbExperimental(_
))
402 if cfg
!(debug_assertions
) => {
403 bug
!("const-prop encountered allocating error: {:?}", error
.kind
);
407 | UndefinedBehavior(_
)
409 | ResourceExhaustion(_
) => {
410 // Ignore these errors.
413 let diagnostic
= error_to_const_error(&self.ecx
, error
);
414 diagnostic
.report_as_lint(
416 "this expression will panic at runtime",
425 self.ecx
.tcx
.span
= DUMMY_SP
;
432 ) -> Option
<Const
<'tcx
>> {
433 self.ecx
.tcx
.span
= c
.span
;
434 match self.ecx
.eval_const_to_op(c
.literal
, None
) {
439 let err
= error_to_const_error(&self.ecx
, error
);
440 err
.report_as_error(self.ecx
.tcx
, "erroneous constant used");
446 fn eval_place(&mut self, place
: &Place
<'tcx
>, source_info
: SourceInfo
) -> Option
<Const
<'tcx
>> {
447 trace
!("eval_place(place={:?})", place
);
448 self.use_ecx(source_info
, |this
| {
449 this
.ecx
.eval_place_to_op(place
, None
)
453 fn eval_operand(&mut self, op
: &Operand
<'tcx
>, source_info
: SourceInfo
) -> Option
<Const
<'tcx
>> {
455 Operand
::Constant(ref c
) => self.eval_constant(c
),
456 | Operand
::Move(ref place
)
457 | Operand
::Copy(ref place
) => self.eval_place(place
, source_info
),
463 rvalue
: &Rvalue
<'tcx
>,
464 place_layout
: TyLayout
<'tcx
>,
465 source_info
: SourceInfo
,
468 let span
= source_info
.span
;
470 // #66397: Don't try to eval into large places as that can cause an OOM
471 if place_layout
.size
>= Size
::from_bytes(MAX_ALLOC_LIMIT
) {
475 let overflow_check
= self.tcx
.sess
.overflow_checks();
477 // Perform any special handling for specific Rvalue types.
478 // Generally, checks here fall into one of two categories:
479 // 1. Additional checking to provide useful lints to the user
480 // - In this case, we will do some validation and then fall through to the
481 // end of the function which evals the assignment.
482 // 2. Working around bugs in other parts of the compiler
483 // - In this case, we'll return `None` from this function to stop evaluation.
485 // Additional checking: if overflow checks are disabled (which is usually the case in
486 // release mode), then we need to do additional checking here to give lints to the user
487 // if an overflow would occur.
488 Rvalue
::UnaryOp(UnOp
::Neg
, arg
) if !overflow_check
=> {
489 trace
!("checking UnaryOp(op = Neg, arg = {:?})", arg
);
491 self.use_ecx(source_info
, |this
| {
492 let ty
= arg
.ty(&this
.local_decls
, this
.tcx
);
494 if ty
.is_integral() {
495 let arg
= this
.ecx
.eval_operand(arg
, None
)?
;
496 let prim
= this
.ecx
.read_immediate(arg
)?
;
497 // Need to do overflow check here: For actual CTFE, MIR
498 // generation emits code that does this before calling the op.
499 if prim
.to_bits()?
== (1 << (prim
.layout
.size
.bits() - 1)) {
500 throw_panic
!(OverflowNeg
)
508 // Additional checking: check for overflows on integer binary operations and report
509 // them to the user as lints.
510 Rvalue
::BinaryOp(op
, left
, right
) => {
511 trace
!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op
, left
, right
);
513 let r
= self.use_ecx(source_info
, |this
| {
514 this
.ecx
.read_immediate(this
.ecx
.eval_operand(right
, None
)?
)
516 if *op
== BinOp
::Shr
|| *op
== BinOp
::Shl
{
517 let left_bits
= place_layout
.size
.bits();
518 let right_size
= r
.layout
.size
;
519 let r_bits
= r
.to_scalar().and_then(|r
| r
.to_bits(right_size
));
520 if r_bits
.map_or(false, |b
| b
>= left_bits
as u128
) {
521 let lint_root
= match &self.source_scopes
[source_info
.scope
].local_data
{
522 ClearCrossCrate
::Set(data
) => data
.lint_root
,
523 ClearCrossCrate
::Clear
=> return None
,
525 let dir
= if *op
== BinOp
::Shr
{
531 ::rustc
::lint
::builtin
::EXCEEDING_BITSHIFTS
,
534 &format
!("attempt to shift {} with overflow", dir
));
539 // If overflow checking is enabled (like in debug mode by default),
540 // then we'll already catch overflow when we evaluate the `Assert` statement
541 // in MIR. However, if overflow checking is disabled, then there won't be any
542 // `Assert` statement and so we have to do additional checking here.
544 self.use_ecx(source_info
, |this
| {
545 let l
= this
.ecx
.read_immediate(this
.ecx
.eval_operand(left
, None
)?
)?
;
546 let (_
, overflow
, _ty
) = this
.ecx
.overflowing_binary_op(*op
, l
, r
)?
;
549 let err
= err_panic
!(Overflow(*op
)).into();
558 // Work around: avoid ICE in miri. FIXME(wesleywiser)
559 // The Miri engine ICEs when taking a reference to an uninitialized unsized
560 // local. There's nothing it can do here: taking a reference needs an allocation
561 // which needs to know the size. Normally that's okay as during execution
562 // (e.g. for CTFE) it can never happen. But here in const_prop
563 // unknown data is uninitialized, so if e.g. a function argument is unsized
564 // and has a reference taken, we get an ICE.
565 Rvalue
::Ref(_
, _
, place_ref
) => {
566 trace
!("checking Ref({:?})", place_ref
);
568 if let Some(local
) = place_ref
.as_local() {
570 if let LocalValue
::Live(_
) = self.ecx
.frame().locals
[local
].value
{
577 trace
!("skipping Ref({:?}) to uninitialized local", place
);
586 self.use_ecx(source_info
, |this
| {
587 trace
!("calling eval_rvalue_into_place(rvalue = {:?}, place = {:?})", rvalue
, place
);
588 this
.ecx
.eval_rvalue_into_place(rvalue
, place
)?
;
593 fn operand_from_scalar(&self, scalar
: Scalar
, ty
: Ty
<'tcx
>, span
: Span
) -> Operand
<'tcx
> {
594 Operand
::Constant(Box
::new(
598 literal
: self.tcx
.mk_const(*ty
::Const
::from_scalar(
607 fn replace_with_const(
609 rval
: &mut Rvalue
<'tcx
>,
611 source_info
: SourceInfo
,
613 trace
!("attepting to replace {:?} with {:?}", rval
, value
);
614 if let Err(e
) = self.ecx
.validate_operand(
617 // FIXME: is ref tracking too expensive?
618 Some(&mut interpret
::RefTracking
::empty()),
620 trace
!("validation error, attempt failed: {:?}", e
);
624 // FIXME> figure out what tho do when try_read_immediate fails
625 let imm
= self.use_ecx(source_info
, |this
| {
626 this
.ecx
.try_read_immediate(value
)
629 if let Some(Ok(imm
)) = imm
{
631 interpret
::Immediate
::Scalar(ScalarMaybeUndef
::Scalar(scalar
)) => {
633 self.operand_from_scalar(scalar
, value
.layout
.ty
, source_info
.span
));
635 Immediate
::ScalarPair(
636 ScalarMaybeUndef
::Scalar(one
),
637 ScalarMaybeUndef
::Scalar(two
)
639 // Found a value represented as a pair. For now only do cont-prop if type of
640 // Rvalue is also a pair with two scalars. The more general case is more
641 // complicated to implement so we'll do it later.
642 let ty
= &value
.layout
.ty
.kind
;
643 // Only do it for tuples
644 if let ty
::Tuple(substs
) = ty
{
645 // Only do it if tuple is also a pair with two scalars
646 if substs
.len() == 2 {
647 let opt_ty1_ty2
= self.use_ecx(source_info
, |this
| {
648 let ty1
= substs
[0].expect_ty();
649 let ty2
= substs
[1].expect_ty();
650 let ty_is_scalar
= |ty
| {
654 .map(|ty
| ty
.details
.abi
.is_scalar())
657 if ty_is_scalar(ty1
) && ty_is_scalar(ty2
) {
664 if let Some(Some((ty1
, ty2
))) = opt_ty1_ty2
{
665 *rval
= Rvalue
::Aggregate(
666 Box
::new(AggregateKind
::Tuple
),
668 self.operand_from_scalar(
669 one
, ty1
, source_info
.span
671 self.operand_from_scalar(
672 two
, ty2
, source_info
.span
685 fn should_const_prop(&mut self, op
: OpTy
<'tcx
>) -> bool
{
686 let mir_opt_level
= self.tcx
.sess
.opts
.debugging_opts
.mir_opt_level
;
688 if mir_opt_level
== 0 {
693 interpret
::Operand
::Immediate(Immediate
::Scalar(ScalarMaybeUndef
::Scalar(s
))) =>
695 interpret
::Operand
::Immediate(Immediate
::ScalarPair(ScalarMaybeUndef
::Scalar(l
),
696 ScalarMaybeUndef
::Scalar(r
))) =>
697 l
.is_bits() && r
.is_bits(),
698 interpret
::Operand
::Indirect(_
) if mir_opt_level
>= 2 => {
699 intern_const_alloc_recursive(
702 op
.assert_mem_place()
703 ).expect("failed to intern alloc");
711 struct CanConstProp
{
712 can_const_prop
: IndexVec
<Local
, bool
>,
713 // false at the beginning, once set, there are not allowed to be any more assignments
714 found_assignment
: IndexVec
<Local
, bool
>,
718 /// returns true if `local` can be propagated
719 fn check(body
: ReadOnlyBodyAndCache
<'_
, '_
>) -> IndexVec
<Local
, bool
> {
720 let mut cpv
= CanConstProp
{
721 can_const_prop
: IndexVec
::from_elem(true, &body
.local_decls
),
722 found_assignment
: IndexVec
::from_elem(false, &body
.local_decls
),
724 for (local
, val
) in cpv
.can_const_prop
.iter_enumerated_mut() {
725 // cannot use args at all
726 // cannot use locals because if x < y { y - x } else { x - y } would
728 // FIXME(oli-obk): lint variables until they are used in a condition
729 // FIXME(oli-obk): lint if return value is constant
730 let local_kind
= body
.local_kind(local
);
731 *val
= local_kind
== LocalKind
::Temp
|| local_kind
== LocalKind
::ReturnPointer
;
734 trace
!("local {:?} can't be propagated because it's not a temporary", local
);
737 cpv
.visit_body(body
);
742 impl<'tcx
> Visitor
<'tcx
> for CanConstProp
{
746 context
: PlaceContext
,
749 use rustc
::mir
::visit
::PlaceContext
::*;
751 // Constants must have at most one write
752 // FIXME(oli-obk): we could be more powerful here, if the multiple writes
753 // only occur in independent execution paths
754 MutatingUse(MutatingUseContext
::Store
) => if self.found_assignment
[local
] {
755 trace
!("local {:?} can't be propagated because of multiple assignments", local
);
756 self.can_const_prop
[local
] = false;
758 self.found_assignment
[local
] = true
760 // Reading constants is allowed an arbitrary number of times
761 NonMutatingUse(NonMutatingUseContext
::Copy
) |
762 NonMutatingUse(NonMutatingUseContext
::Move
) |
763 NonMutatingUse(NonMutatingUseContext
::Inspect
) |
764 NonMutatingUse(NonMutatingUseContext
::Projection
) |
765 MutatingUse(MutatingUseContext
::Projection
) |
768 trace
!("local {:?} can't be propagaged because it's used: {:?}", local
, context
);
769 self.can_const_prop
[local
] = false;
775 impl<'mir
, 'tcx
> MutVisitor
<'tcx
> for ConstPropagator
<'mir
, 'tcx
> {
776 fn tcx(&self) -> TyCtxt
<'tcx
> {
782 constant
: &mut Constant
<'tcx
>,
785 trace
!("visit_constant: {:?}", constant
);
786 self.super_constant(constant
, location
);
787 self.eval_constant(constant
);
792 statement
: &mut Statement
<'tcx
>,
795 trace
!("visit_statement: {:?}", statement
);
796 if let StatementKind
::Assign(box(ref place
, ref mut rval
)) = statement
.kind
{
797 let place_ty
: Ty
<'tcx
> = place
798 .ty(&self.local_decls
, self.tcx
)
800 if let Ok(place_layout
) = self.tcx
.layout_of(self.param_env
.and(place_ty
)) {
801 if let Some(local
) = place
.as_local() {
802 let source
= statement
.source_info
;
803 if let Some(()) = self.const_prop(rval
, place_layout
, source
, place
) {
804 if self.can_const_prop
[local
] {
805 trace
!("propagated into {:?}", local
);
807 if let Some(value
) = self.get_const(local
) {
808 if self.should_const_prop(value
) {
809 trace
!("replacing {:?} with {:?}", rval
, value
);
810 self.replace_with_const(
813 statement
.source_info
,
818 trace
!("can't propagate into {:?}", local
);
819 if local
!= RETURN_PLACE
{
820 self.remove_const(local
);
827 match statement
.kind
{
828 StatementKind
::StorageLive(local
) |
829 StatementKind
::StorageDead(local
) if self.can_const_prop
[local
] => {
830 let frame
= self.ecx
.frame_mut();
831 frame
.locals
[local
].value
=
832 if let StatementKind
::StorageLive(_
) = statement
.kind
{
833 LocalValue
::Uninitialized
842 self.super_statement(statement
, location
);
847 terminator
: &mut Terminator
<'tcx
>,
850 self.super_terminator(terminator
, location
);
851 let source_info
= terminator
.source_info
;
852 match &mut terminator
.kind
{
853 TerminatorKind
::Assert { expected, ref msg, ref mut cond, .. }
=> {
854 if let Some(value
) = self.eval_operand(&cond
, source_info
) {
855 trace
!("assertion on {:?} should be {:?}", value
, expected
);
856 let expected
= ScalarMaybeUndef
::from(Scalar
::from_bool(*expected
));
857 let value_const
= self.ecx
.read_scalar(value
).unwrap();
858 if expected
!= value_const
{
859 // poison all places this operand references so that further code
860 // doesn't use the invalid value
862 Operand
::Move(ref place
) | Operand
::Copy(ref place
) => {
863 if let PlaceBase
::Local(local
) = place
.base
{
864 self.remove_const(local
);
867 Operand
::Constant(_
) => {}
869 let span
= terminator
.source_info
.span
;
873 .as_local_hir_id(self.source
.def_id())
874 .expect("some part of a failing const eval must be local");
875 let msg
= match msg
{
876 PanicInfo
::Overflow(_
) |
877 PanicInfo
::OverflowNeg
|
878 PanicInfo
::DivisionByZero
|
879 PanicInfo
::RemainderByZero
=>
880 msg
.description().to_owned(),
881 PanicInfo
::BoundsCheck { ref len, ref index }
=> {
883 .eval_operand(len
, source_info
)
884 .expect("len must be const");
885 let len
= match self.ecx
.read_scalar(len
) {
886 Ok(ScalarMaybeUndef
::Scalar(Scalar
::Raw
{
889 other
=> bug
!("const len not primitive: {:?}", other
),
892 .eval_operand(index
, source_info
)
893 .expect("index must be const");
894 let index
= match self.ecx
.read_scalar(index
) {
895 Ok(ScalarMaybeUndef
::Scalar(Scalar
::Raw
{
898 other
=> bug
!("const index not primitive: {:?}", other
),
901 "index out of bounds: \
902 the len is {} but the index is {}",
907 // Need proper const propagator for these
911 ::rustc
::lint
::builtin
::CONST_ERR
,
917 if self.should_const_prop(value
) {
918 if let ScalarMaybeUndef
::Scalar(scalar
) = value_const
{
919 *cond
= self.operand_from_scalar(
929 TerminatorKind
::SwitchInt { ref mut discr, switch_ty, .. }
=> {
930 if let Some(value
) = self.eval_operand(&discr
, source_info
) {
931 if self.should_const_prop(value
) {
932 if let ScalarMaybeUndef
::Scalar(scalar
) =
933 self.ecx
.read_scalar(value
).unwrap() {
934 *discr
= self.operand_from_scalar(scalar
, switch_ty
, source_info
.span
);
939 //none of these have Operands to const-propagate
940 TerminatorKind
::Goto { .. }
|
941 TerminatorKind
::Resume
|
942 TerminatorKind
::Abort
|
943 TerminatorKind
::Return
|
944 TerminatorKind
::Unreachable
|
945 TerminatorKind
::Drop { .. }
|
946 TerminatorKind
::DropAndReplace { .. }
|
947 TerminatorKind
::Yield { .. }
|
948 TerminatorKind
::GeneratorDrop
|
949 TerminatorKind
::FalseEdges { .. }
|
950 TerminatorKind
::FalseUnwind { .. }
=> { }
951 //FIXME(wesleywiser) Call does have Operands that could be const-propagated
952 TerminatorKind
::Call { .. }
=> { }