]> git.proxmox.com Git - rustc.git/blob - src/librustc_mir/transform/const_prop.rs
New upstream version 1.41.1+dfsg1
[rustc.git] / src / librustc_mir / transform / const_prop.rs
1 //! Propagates constants for early reporting of statically known
2 //! assertion failures
3
4 use std::borrow::Cow;
5 use std::cell::Cell;
6
7 use rustc::hir::def::DefKind;
8 use rustc::hir::def_id::DefId;
9 use rustc::mir::{
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
14 };
15 use rustc::mir::visit::{
16 Visitor, PlaceContext, MutatingUseContext, MutVisitor, NonMutatingUseContext,
17 };
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,
27 };
28
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,
35 };
36 use crate::const_eval::error_to_const_error;
37 use crate::transform::{MirPass, MirSource};
38
39 /// The maximum number of bytes that we'll allocate space for a return value.
40 const MAX_ALLOC_LIMIT: u64 = 1024;
41
42 pub struct ConstProp;
43
44 impl<'tcx> MirPass<'tcx> for ConstProp {
45 fn run_pass(
46 &self, tcx: TyCtxt<'tcx>, source: MirSource<'tcx>, body: &mut BodyAndCache<'tcx>
47 ) {
48 // will be evaluated by miri and produce its errors there
49 if source.promoted.is_some() {
50 return;
51 }
52
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");
56
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,
60 _ => false,
61 };
62
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());
67 return
68 }
69
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.
73 if is_generator {
74 trace!("ConstProp skipped for generator {:?}", source.def_id());
75 return
76 }
77
78 trace!("ConstProp starting for {:?}", source.def_id());
79
80 let dummy_body =
81 &Body::new(
82 body.basic_blocks().clone(),
83 body.source_scopes.clone(),
84 body.local_decls.clone(),
85 Default::default(),
86 body.arg_count,
87 Default::default(),
88 tcx.def_span(source.def_id()),
89 Default::default(),
90 body.generator_kind,
91 );
92
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(
98 read_only!(body),
99 dummy_body,
100 tcx,
101 source
102 );
103 optimization_finder.visit_body(body);
104
105 trace!("ConstProp done for {:?}", source.def_id());
106 }
107 }
108
109 struct ConstPropMachine;
110
111 impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine {
112 type MemoryKinds = !;
113 type PointerTag = ();
114 type ExtraFnVal = !;
115
116 type FrameExtra = ();
117 type MemoryExtra = ();
118 type AllocExtra = ();
119
120 type MemoryMap = FxHashMap<AllocId, (MemoryKind<!>, Allocation)>;
121
122 const STATIC_KIND: Option<!> = None;
123
124 const CHECK_ALIGN: bool = false;
125
126 #[inline(always)]
127 fn enforce_validity(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
128 false
129 }
130
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>>> {
138 Ok(None)
139 }
140
141 fn call_extra_fn(
142 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
143 fn_val: !,
144 _args: &[OpTy<'tcx>],
145 _ret: Option<(PlaceTy<'tcx>, BasicBlock)>,
146 _unwind: Option<BasicBlock>
147 ) -> InterpResult<'tcx> {
148 match fn_val {}
149 }
150
151 fn call_intrinsic(
152 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
153 _span: Span,
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"));
160 }
161
162 fn assert_panic(
163 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
164 _span: Span,
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");
169 }
170
171 fn ptr_to_int(
172 _mem: &Memory<'mir, 'tcx, Self>,
173 _ptr: Pointer,
174 ) -> InterpResult<'tcx, u64> {
175 throw_unsup!(ConstPropUnsupported("ptr-to-int casts aren't supported in ConstProp"));
176 }
177
178 fn binary_ptr_op(
179 _ecx: &InterpCx<'mir, 'tcx, Self>,
180 _bin_op: BinOp,
181 _left: ImmTy<'tcx>,
182 _right: ImmTy<'tcx>,
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 \
186 in ConstProp"));
187 }
188
189 fn find_foreign_static(
190 _tcx: TyCtxt<'tcx>,
191 _def_id: DefId,
192 ) -> InterpResult<'tcx, Cow<'tcx, Allocation<Self::PointerTag>>> {
193 throw_unsup!(ReadForeignStatic)
194 }
195
196 #[inline(always)]
197 fn init_allocation_extra<'b>(
198 _memory_extra: &(),
199 _id: AllocId,
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
204 (alloc, ())
205 }
206
207 #[inline(always)]
208 fn tag_static_base_pointer(
209 _memory_extra: &(),
210 _id: AllocId,
211 ) -> Self::PointerTag {
212 ()
213 }
214
215 fn box_alloc(
216 _ecx: &mut InterpCx<'mir, 'tcx, Self>,
217 _dest: PlaceTy<'tcx>,
218 ) -> InterpResult<'tcx> {
219 throw_unsup!(ConstPropUnsupported("can't const prop `box` keyword"));
220 }
221
222 fn access_local(
223 _ecx: &InterpCx<'mir, 'tcx, Self>,
224 frame: &Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
225 local: Local,
226 ) -> InterpResult<'tcx, InterpOperand<Self::PointerTag>> {
227 let l = &frame.locals[local];
228
229 if l.value == LocalValue::Uninitialized {
230 throw_unsup!(ConstPropUnsupported("tried to access an uninitialized local"));
231 }
232
233 l.access()
234 }
235
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"));
243 }
244
245 Ok(())
246 }
247
248 fn before_terminator(_ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
249 Ok(())
250 }
251
252 #[inline(always)]
253 fn stack_push(_ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
254 Ok(())
255 }
256 }
257
258 type Const<'tcx> = OpTy<'tcx>;
259
260 /// Finds optimization opportunities on the MIR.
261 struct ConstPropagator<'mir, 'tcx> {
262 ecx: InterpCx<'mir, 'tcx, ConstPropMachine>,
263 tcx: TyCtxt<'tcx>,
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, ()>>,
272 }
273
274 impl<'mir, 'tcx> LayoutOf for ConstPropagator<'mir, 'tcx> {
275 type Ty = Ty<'tcx>;
276 type TyLayout = Result<TyLayout<'tcx>, LayoutError<'tcx>>;
277
278 fn layout_of(&self, ty: Ty<'tcx>) -> Self::TyLayout {
279 self.tcx.layout_of(self.param_env.and(ty))
280 }
281 }
282
283 impl<'mir, 'tcx> HasDataLayout for ConstPropagator<'mir, 'tcx> {
284 #[inline]
285 fn data_layout(&self) -> &TargetDataLayout {
286 &self.tcx.data_layout
287 }
288 }
289
290 impl<'mir, 'tcx> HasTyCtxt<'tcx> for ConstPropagator<'mir, 'tcx> {
291 #[inline]
292 fn tcx(&self) -> TyCtxt<'tcx> {
293 self.tcx
294 }
295 }
296
297 impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
298 fn new(
299 body: ReadOnlyBodyAndCache<'_, 'tcx>,
300 dummy_body: &'mir Body<'tcx>,
301 tcx: TyCtxt<'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);
309
310 let substs = &InternalSubsts::identity_for_item(tcx, def_id);
311
312 let ret =
313 ecx
314 .layout_of(body.return_ty().subst(tcx, substs))
315 .ok()
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));
321
322 ecx.push_stack_frame(
323 Instance::new(def_id, substs),
324 span,
325 dummy_body,
326 ret.map(Into::into),
327 StackPopCleanup::None {
328 cleanup: false,
329 },
330 ).expect("failed to push initial stack frame");
331
332 ConstPropagator {
333 ecx,
334 tcx,
335 source,
336 param_env,
337 can_const_prop,
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),
344 }
345 }
346
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()),
353 _ => self.ret,
354 };
355 }
356
357 self.ecx.access_local(self.ecx.frame(), local, None).ok()
358 }
359
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),
364 };
365 }
366
367 fn use_ecx<F, T>(
368 &mut self,
369 source_info: SourceInfo,
370 f: F
371 ) -> Option<T>
372 where
373 F: FnOnce(&mut Self) -> InterpResult<'tcx, T>,
374 {
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,
382 };
383 let r = match f(self) {
384 Ok(val) => Some(val),
385 Err(error) => {
386 use rustc::mir::interpret::{
387 UnsupportedOpInfo,
388 UndefinedBehaviorInfo,
389 InterpError::*
390 };
391 match error.kind {
392 MachineStop(_) => bug!("ConstProp does not stop"),
393
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);
404 }
405
406 Unsupported(_)
407 | UndefinedBehavior(_)
408 | InvalidProgram(_)
409 | ResourceExhaustion(_) => {
410 // Ignore these errors.
411 }
412 Panic(_) => {
413 let diagnostic = error_to_const_error(&self.ecx, error);
414 diagnostic.report_as_lint(
415 self.ecx.tcx,
416 "this expression will panic at runtime",
417 lint_root,
418 None,
419 );
420 }
421 }
422 None
423 },
424 };
425 self.ecx.tcx.span = DUMMY_SP;
426 r
427 }
428
429 fn eval_constant(
430 &mut self,
431 c: &Constant<'tcx>,
432 ) -> Option<Const<'tcx>> {
433 self.ecx.tcx.span = c.span;
434 match self.ecx.eval_const_to_op(c.literal, None) {
435 Ok(op) => {
436 Some(op)
437 },
438 Err(error) => {
439 let err = error_to_const_error(&self.ecx, error);
440 err.report_as_error(self.ecx.tcx, "erroneous constant used");
441 None
442 },
443 }
444 }
445
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)
450 })
451 }
452
453 fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<Const<'tcx>> {
454 match *op {
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),
458 }
459 }
460
461 fn const_prop(
462 &mut self,
463 rvalue: &Rvalue<'tcx>,
464 place_layout: TyLayout<'tcx>,
465 source_info: SourceInfo,
466 place: &Place<'tcx>,
467 ) -> Option<()> {
468 let span = source_info.span;
469
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) {
472 return None;
473 }
474
475 let overflow_check = self.tcx.sess.overflow_checks();
476
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.
484 match rvalue {
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);
490
491 self.use_ecx(source_info, |this| {
492 let ty = arg.ty(&this.local_decls, this.tcx);
493
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)
501 }
502 }
503
504 Ok(())
505 })?;
506 }
507
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);
512
513 let r = self.use_ecx(source_info, |this| {
514 this.ecx.read_immediate(this.ecx.eval_operand(right, None)?)
515 })?;
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,
524 };
525 let dir = if *op == BinOp::Shr {
526 "right"
527 } else {
528 "left"
529 };
530 self.tcx.lint_hir(
531 ::rustc::lint::builtin::EXCEEDING_BITSHIFTS,
532 lint_root,
533 span,
534 &format!("attempt to shift {} with overflow", dir));
535 return None;
536 }
537 }
538
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.
543 if !overflow_check {
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)?;
547
548 if overflow {
549 let err = err_panic!(Overflow(*op)).into();
550 return Err(err);
551 }
552
553 Ok(())
554 })?;
555 }
556 }
557
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);
567
568 if let Some(local) = place_ref.as_local() {
569 let alive =
570 if let LocalValue::Live(_) = self.ecx.frame().locals[local].value {
571 true
572 } else {
573 false
574 };
575
576 if !alive {
577 trace!("skipping Ref({:?}) to uninitialized local", place);
578 return None;
579 }
580 }
581 }
582
583 _ => { }
584 }
585
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)?;
589 Ok(())
590 })
591 }
592
593 fn operand_from_scalar(&self, scalar: Scalar, ty: Ty<'tcx>, span: Span) -> Operand<'tcx> {
594 Operand::Constant(Box::new(
595 Constant {
596 span,
597 user_ty: None,
598 literal: self.tcx.mk_const(*ty::Const::from_scalar(
599 self.tcx,
600 scalar,
601 ty,
602 ))
603 }
604 ))
605 }
606
607 fn replace_with_const(
608 &mut self,
609 rval: &mut Rvalue<'tcx>,
610 value: Const<'tcx>,
611 source_info: SourceInfo,
612 ) {
613 trace!("attepting to replace {:?} with {:?}", rval, value);
614 if let Err(e) = self.ecx.validate_operand(
615 value,
616 vec![],
617 // FIXME: is ref tracking too expensive?
618 Some(&mut interpret::RefTracking::empty()),
619 ) {
620 trace!("validation error, attempt failed: {:?}", e);
621 return;
622 }
623
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)
627 });
628
629 if let Some(Ok(imm)) = imm {
630 match *imm {
631 interpret::Immediate::Scalar(ScalarMaybeUndef::Scalar(scalar)) => {
632 *rval = Rvalue::Use(
633 self.operand_from_scalar(scalar, value.layout.ty, source_info.span));
634 },
635 Immediate::ScalarPair(
636 ScalarMaybeUndef::Scalar(one),
637 ScalarMaybeUndef::Scalar(two)
638 ) => {
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| {
651 this.ecx
652 .layout_of(ty)
653 .ok()
654 .map(|ty| ty.details.abi.is_scalar())
655 == Some(true)
656 };
657 if ty_is_scalar(ty1) && ty_is_scalar(ty2) {
658 Ok(Some((ty1, ty2)))
659 } else {
660 Ok(None)
661 }
662 });
663
664 if let Some(Some((ty1, ty2))) = opt_ty1_ty2 {
665 *rval = Rvalue::Aggregate(
666 Box::new(AggregateKind::Tuple),
667 vec![
668 self.operand_from_scalar(
669 one, ty1, source_info.span
670 ),
671 self.operand_from_scalar(
672 two, ty2, source_info.span
673 ),
674 ],
675 );
676 }
677 }
678 }
679 },
680 _ => { }
681 }
682 }
683 }
684
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;
687
688 if mir_opt_level == 0 {
689 return false;
690 }
691
692 match *op {
693 interpret::Operand::Immediate(Immediate::Scalar(ScalarMaybeUndef::Scalar(s))) =>
694 s.is_bits(),
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(
700 &mut self.ecx,
701 None,
702 op.assert_mem_place()
703 ).expect("failed to intern alloc");
704 true
705 },
706 _ => false
707 }
708 }
709 }
710
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>,
715 }
716
717 impl CanConstProp {
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),
723 };
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
727 // lint for x != y
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;
732
733 if !*val {
734 trace!("local {:?} can't be propagated because it's not a temporary", local);
735 }
736 }
737 cpv.visit_body(body);
738 cpv.can_const_prop
739 }
740 }
741
742 impl<'tcx> Visitor<'tcx> for CanConstProp {
743 fn visit_local(
744 &mut self,
745 &local: &Local,
746 context: PlaceContext,
747 _: Location,
748 ) {
749 use rustc::mir::visit::PlaceContext::*;
750 match context {
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;
757 } else {
758 self.found_assignment[local] = true
759 },
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) |
766 NonUse(_) => {},
767 _ => {
768 trace!("local {:?} can't be propagaged because it's used: {:?}", local, context);
769 self.can_const_prop[local] = false;
770 },
771 }
772 }
773 }
774
775 impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> {
776 fn tcx(&self) -> TyCtxt<'tcx> {
777 self.tcx
778 }
779
780 fn visit_constant(
781 &mut self,
782 constant: &mut Constant<'tcx>,
783 location: Location,
784 ) {
785 trace!("visit_constant: {:?}", constant);
786 self.super_constant(constant, location);
787 self.eval_constant(constant);
788 }
789
790 fn visit_statement(
791 &mut self,
792 statement: &mut Statement<'tcx>,
793 location: Location,
794 ) {
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)
799 .ty;
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);
806
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(
811 rval,
812 value,
813 statement.source_info,
814 );
815 }
816 }
817 } else {
818 trace!("can't propagate into {:?}", local);
819 if local != RETURN_PLACE {
820 self.remove_const(local);
821 }
822 }
823 }
824 }
825 }
826 } else {
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
834 } else {
835 LocalValue::Dead
836 };
837 }
838 _ => {}
839 }
840 }
841
842 self.super_statement(statement, location);
843 }
844
845 fn visit_terminator(
846 &mut self,
847 terminator: &mut Terminator<'tcx>,
848 location: Location,
849 ) {
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
861 match cond {
862 Operand::Move(ref place) | Operand::Copy(ref place) => {
863 if let PlaceBase::Local(local) = place.base {
864 self.remove_const(local);
865 }
866 },
867 Operand::Constant(_) => {}
868 }
869 let span = terminator.source_info.span;
870 let hir_id = self
871 .tcx
872 .hir()
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 } => {
882 let len = self
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 {
887 data, ..
888 })) => data,
889 other => bug!("const len not primitive: {:?}", other),
890 };
891 let index = self
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 {
896 data, ..
897 })) => data,
898 other => bug!("const index not primitive: {:?}", other),
899 };
900 format!(
901 "index out of bounds: \
902 the len is {} but the index is {}",
903 len,
904 index,
905 )
906 },
907 // Need proper const propagator for these
908 _ => return,
909 };
910 self.tcx.lint_hir(
911 ::rustc::lint::builtin::CONST_ERR,
912 hir_id,
913 span,
914 &msg,
915 );
916 } else {
917 if self.should_const_prop(value) {
918 if let ScalarMaybeUndef::Scalar(scalar) = value_const {
919 *cond = self.operand_from_scalar(
920 scalar,
921 self.tcx.types.bool,
922 source_info.span,
923 );
924 }
925 }
926 }
927 }
928 },
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);
935 }
936 }
937 }
938 },
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 { .. } => { }
953 }
954 }
955 }