]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/transform/promote_consts.rs
Rollup merge of #65664 - anp:panic-location, r=eddyb
[rust.git] / src / librustc_mir / transform / promote_consts.rs
1 //! A pass that promotes borrows of constant rvalues.
2 //!
3 //! The rvalues considered constant are trees of temps,
4 //! each with exactly one initialization, and holding
5 //! a constant value with no interior mutability.
6 //! They are placed into a new MIR constant body in
7 //! `promoted` and the borrow rvalue is replaced with
8 //! a `Literal::Promoted` using the index into `promoted`
9 //! of that constant MIR.
10 //!
11 //! This pass assumes that every use is dominated by an
12 //! initialization and can otherwise silence errors, if
13 //! move analysis runs after promotion on broken MIR.
14
15 use rustc::hir::def_id::DefId;
16 use rustc::mir::*;
17 use rustc::mir::interpret::ConstValue;
18 use rustc::mir::visit::{PlaceContext, MutatingUseContext, MutVisitor, Visitor};
19 use rustc::mir::traversal::ReversePostorder;
20 use rustc::ty::{self, List, TyCtxt};
21 use rustc::ty::subst::InternalSubsts;
22 use rustc::ty::cast::CastTy;
23 use syntax::ast::LitKind;
24 use syntax::symbol::sym;
25 use syntax_pos::{Span, DUMMY_SP};
26
27 use rustc_index::vec::{IndexVec, Idx};
28 use rustc_target::spec::abi::Abi;
29
30 use std::{iter, mem, usize};
31
32 use crate::transform::check_consts::{qualifs, Item, ConstKind, is_lang_panic_fn};
33
34 /// State of a temporary during collection and promotion.
35 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
36 pub enum TempState {
37     /// No references to this temp.
38     Undefined,
39     /// One direct assignment and any number of direct uses.
40     /// A borrow of this temp is promotable if the assigned
41     /// value is qualified as constant.
42     Defined {
43         location: Location,
44         uses: usize
45     },
46     /// Any other combination of assignments/uses.
47     Unpromotable,
48     /// This temp was part of an rvalue which got extracted
49     /// during promotion and needs cleanup.
50     PromotedOut
51 }
52
53 impl TempState {
54     pub fn is_promotable(&self) -> bool {
55         debug!("is_promotable: self={:?}", self);
56         if let TempState::Defined { .. } = *self {
57             true
58         } else {
59             false
60         }
61     }
62 }
63
64 /// A "root candidate" for promotion, which will become the
65 /// returned value in a promoted MIR, unless it's a subset
66 /// of a larger candidate.
67 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
68 pub enum Candidate {
69     /// Borrow of a constant temporary.
70     Ref(Location),
71
72     /// Promotion of the `x` in `[x; 32]`.
73     Repeat(Location),
74
75     /// Currently applied to function calls where the callee has the unstable
76     /// `#[rustc_args_required_const]` attribute as well as the SIMD shuffle
77     /// intrinsic. The intrinsic requires the arguments are indeed constant and
78     /// the attribute currently provides the semantic requirement that arguments
79     /// must be constant.
80     Argument { bb: BasicBlock, index: usize },
81 }
82
83 fn args_required_const(tcx: TyCtxt<'_>, def_id: DefId) -> Option<Vec<usize>> {
84     let attrs = tcx.get_attrs(def_id);
85     let attr = attrs.iter().find(|a| a.check_name(sym::rustc_args_required_const))?;
86     let mut ret = vec![];
87     for meta in attr.meta_item_list()? {
88         match meta.literal()?.kind {
89             LitKind::Int(a, _) => { ret.push(a as usize); }
90             _ => return None,
91         }
92     }
93     Some(ret)
94 }
95
96 struct Collector<'a, 'tcx> {
97     tcx: TyCtxt<'tcx>,
98     body: &'a Body<'tcx>,
99     temps: IndexVec<Local, TempState>,
100     candidates: Vec<Candidate>,
101     span: Span,
102 }
103
104 impl<'tcx> Visitor<'tcx> for Collector<'_, 'tcx> {
105     fn visit_local(&mut self,
106                    &index: &Local,
107                    context: PlaceContext,
108                    location: Location) {
109         debug!("visit_local: index={:?} context={:?} location={:?}", index, context, location);
110         // We're only interested in temporaries and the return place
111         match self.body.local_kind(index) {
112             | LocalKind::Temp
113             | LocalKind::ReturnPointer
114             => {},
115             | LocalKind::Arg
116             | LocalKind::Var
117             => return,
118         }
119
120         // Ignore drops, if the temp gets promoted,
121         // then it's constant and thus drop is noop.
122         // Non-uses are also irrelevent.
123         if context.is_drop() || !context.is_use() {
124             debug!(
125                 "visit_local: context.is_drop={:?} context.is_use={:?}",
126                 context.is_drop(), context.is_use(),
127             );
128             return;
129         }
130
131         let temp = &mut self.temps[index];
132         debug!("visit_local: temp={:?}", temp);
133         if *temp == TempState::Undefined {
134             match context {
135                 PlaceContext::MutatingUse(MutatingUseContext::Store) |
136                 PlaceContext::MutatingUse(MutatingUseContext::Call) => {
137                     *temp = TempState::Defined {
138                         location,
139                         uses: 0
140                     };
141                     return;
142                 }
143                 _ => { /* mark as unpromotable below */ }
144             }
145         } else if let TempState::Defined { ref mut uses, .. } = *temp {
146             // We always allow borrows, even mutable ones, as we need
147             // to promote mutable borrows of some ZSTs e.g., `&mut []`.
148             let allowed_use = context.is_borrow() || context.is_nonmutating_use();
149             debug!("visit_local: allowed_use={:?}", allowed_use);
150             if allowed_use {
151                 *uses += 1;
152                 return;
153             }
154             /* mark as unpromotable below */
155         }
156         *temp = TempState::Unpromotable;
157     }
158
159     fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
160         self.super_rvalue(rvalue, location);
161
162         match *rvalue {
163             Rvalue::Ref(..) => {
164                 self.candidates.push(Candidate::Ref(location));
165             }
166             Rvalue::Repeat(..) if self.tcx.features().const_in_array_repeat_expressions => {
167                 // FIXME(#49147) only promote the element when it isn't `Copy`
168                 // (so that code that can copy it at runtime is unaffected).
169                 self.candidates.push(Candidate::Repeat(location));
170             }
171             _ => {}
172         }
173     }
174
175     fn visit_terminator_kind(&mut self,
176                              kind: &TerminatorKind<'tcx>,
177                              location: Location) {
178         self.super_terminator_kind(kind, location);
179
180         if let TerminatorKind::Call { ref func, .. } = *kind {
181             if let ty::FnDef(def_id, _) = func.ty(self.body, self.tcx).kind {
182                 let fn_sig = self.tcx.fn_sig(def_id);
183                 if let Abi::RustIntrinsic | Abi::PlatformIntrinsic = fn_sig.abi() {
184                     let name = self.tcx.item_name(def_id);
185                     // FIXME(eddyb) use `#[rustc_args_required_const(2)]` for shuffles.
186                     if name.as_str().starts_with("simd_shuffle") {
187                         self.candidates.push(Candidate::Argument {
188                             bb: location.block,
189                             index: 2,
190                         });
191                     }
192                 }
193
194                 if let Some(constant_args) = args_required_const(self.tcx, def_id) {
195                     for index in constant_args {
196                         self.candidates.push(Candidate::Argument { bb: location.block, index });
197                     }
198                 }
199             }
200         }
201     }
202
203     fn visit_source_info(&mut self, source_info: &SourceInfo) {
204         self.span = source_info.span;
205     }
206 }
207
208 pub fn collect_temps_and_candidates(
209     tcx: TyCtxt<'tcx>,
210     body: &Body<'tcx>,
211     rpo: &mut ReversePostorder<'_, 'tcx>,
212 ) -> (IndexVec<Local, TempState>, Vec<Candidate>) {
213     let mut collector = Collector {
214         tcx,
215         body,
216         temps: IndexVec::from_elem(TempState::Undefined, &body.local_decls),
217         candidates: vec![],
218         span: body.span,
219     };
220     for (bb, data) in rpo {
221         collector.visit_basic_block_data(bb, data);
222     }
223     (collector.temps, collector.candidates)
224 }
225
226 /// Checks whether locals that appear in a promotion context (`Candidate`) are actually promotable.
227 ///
228 /// This wraps an `Item`, and has access to all fields of that `Item` via `Deref` coercion.
229 struct Validator<'a, 'tcx> {
230     item: Item<'a, 'tcx>,
231     temps: &'a IndexVec<Local, TempState>,
232
233     /// Explicit promotion happens e.g. for constant arguments declared via
234     /// `rustc_args_required_const`.
235     /// Implicit promotion has almost the same rules, except that disallows `const fn`
236     /// except for those marked `#[rustc_promotable]`. This is to avoid changing
237     /// a legitimate run-time operation into a failing compile-time operation
238     /// e.g. due to addresses being compared inside the function.
239     explicit: bool,
240 }
241
242 impl std::ops::Deref for Validator<'a, 'tcx> {
243     type Target = Item<'a, 'tcx>;
244
245     fn deref(&self) -> &Self::Target {
246         &self.item
247     }
248 }
249
250 struct Unpromotable;
251
252 impl<'tcx> Validator<'_, 'tcx> {
253     fn validate_candidate(&self, candidate: Candidate) -> Result<(), Unpromotable> {
254         match candidate {
255             Candidate::Ref(loc) => {
256                 assert!(!self.explicit);
257
258                 let statement = &self.body[loc.block].statements[loc.statement_index];
259                 match &statement.kind {
260                     StatementKind::Assign(box(_, Rvalue::Ref(_, kind, place))) => {
261                         match kind {
262                             BorrowKind::Shared | BorrowKind::Mut { .. } => {}
263
264                             // FIXME(eddyb) these aren't promoted here but *could*
265                             // be promoted as part of a larger value because
266                             // `validate_rvalue`  doesn't check them, need to
267                             // figure out what is the intended behavior.
268                             BorrowKind::Shallow | BorrowKind::Unique => return Err(Unpromotable),
269                         }
270
271                         // We can only promote interior borrows of promotable temps (non-temps
272                         // don't get promoted anyway).
273                         let base = match place.base {
274                             PlaceBase::Local(local) => local,
275                             _ => return Err(Unpromotable),
276                         };
277                         self.validate_local(base)?;
278
279                         if place.projection.contains(&ProjectionElem::Deref) {
280                             return Err(Unpromotable);
281                         }
282
283                         let mut has_mut_interior =
284                             self.qualif_local::<qualifs::HasMutInterior>(base);
285                         // HACK(eddyb) this should compute the same thing as
286                         // `<HasMutInterior as Qualif>::in_projection` from
287                         // `check_consts::qualifs` but without recursion.
288                         if has_mut_interior {
289                             // This allows borrowing fields which don't have
290                             // `HasMutInterior`, from a type that does, e.g.:
291                             // `let _: &'static _ = &(Cell::new(1), 2).1;`
292                             let mut place_projection = &place.projection[..];
293                             // FIXME(eddyb) use a forward loop instead of a reverse one.
294                             while let [proj_base @ .., elem] = place_projection {
295                                 // FIXME(eddyb) this is probably excessive, with
296                                 // the exception of `union` member accesses.
297                                 let ty =
298                                     Place::ty_from(&place.base, proj_base, self.body, self.tcx)
299                                         .projection_ty(self.tcx, elem)
300                                         .ty;
301                                 if ty.is_freeze(self.tcx, self.param_env, DUMMY_SP) {
302                                     has_mut_interior = false;
303                                     break;
304                                 }
305
306                                 place_projection = proj_base;
307                             }
308                         }
309
310                         // FIXME(eddyb) this duplicates part of `validate_rvalue`.
311                         if has_mut_interior {
312                             return Err(Unpromotable);
313                         }
314                         if self.qualif_local::<qualifs::NeedsDrop>(base) {
315                             return Err(Unpromotable);
316                         }
317
318                         if let BorrowKind::Mut { .. } = kind {
319                             let ty = place.ty(self.body, self.tcx).ty;
320
321                             // In theory, any zero-sized value could be borrowed
322                             // mutably without consequences. However, only &mut []
323                             // is allowed right now, and only in functions.
324                             if self.const_kind == Some(ConstKind::StaticMut) {
325                                 // Inside a `static mut`, &mut [...] is also allowed.
326                                 match ty.kind {
327                                     ty::Array(..) | ty::Slice(_) => {}
328                                     _ => return Err(Unpromotable),
329                                 }
330                             } else if let ty::Array(_, len) = ty.kind {
331                                 // FIXME(eddyb) the `self.is_non_const_fn` condition
332                                 // seems unnecessary, given that this is merely a ZST.
333                                 match len.try_eval_usize(self.tcx, self.param_env) {
334                                     Some(0) if self.const_kind.is_none() => {},
335                                     _ => return Err(Unpromotable),
336                                 }
337                             } else {
338                                 return Err(Unpromotable);
339                             }
340                         }
341
342                         Ok(())
343                     }
344                     _ => bug!()
345                 }
346             }
347             Candidate::Repeat(loc) => {
348                 assert!(!self.explicit);
349
350                 let statement = &self.body[loc.block].statements[loc.statement_index];
351                 match &statement.kind {
352                     StatementKind::Assign(box(_, Rvalue::Repeat(ref operand, _))) => {
353                         if !self.tcx.features().const_in_array_repeat_expressions {
354                             return Err(Unpromotable);
355                         }
356
357                         self.validate_operand(operand)
358                     }
359                     _ => bug!()
360                 }
361             },
362             Candidate::Argument { bb, index } => {
363                 assert!(self.explicit);
364
365                 let terminator = self.body[bb].terminator();
366                 match &terminator.kind {
367                     TerminatorKind::Call { args, .. } => {
368                         self.validate_operand(&args[index])
369                     }
370                     _ => bug!()
371                 }
372             }
373         }
374     }
375
376     // FIXME(eddyb) maybe cache this?
377     fn qualif_local<Q: qualifs::Qualif>(&self, local: Local) -> bool {
378         let per_local = &|l| self.qualif_local::<Q>(l);
379
380         if let TempState::Defined { location: loc, .. } = self.temps[local] {
381             let num_stmts = self.body[loc.block].statements.len();
382
383             if loc.statement_index < num_stmts {
384                 let statement = &self.body[loc.block].statements[loc.statement_index];
385                 match &statement.kind {
386                     StatementKind::Assign(box(_, rhs)) => {
387                         Q::in_rvalue(&self.item, per_local, rhs)
388                     }
389                     _ => {
390                         span_bug!(statement.source_info.span, "{:?} is not an assignment",
391                                 statement);
392                     }
393                 }
394             } else {
395                 let terminator = self.body[loc.block].terminator();
396                 match &terminator.kind {
397                     TerminatorKind::Call { func, args, .. } => {
398                         let return_ty = self.body.local_decls[local].ty;
399                         Q::in_call(&self.item, per_local, func, args, return_ty)
400                     }
401                     kind => {
402                         span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
403                     }
404                 }
405             }
406         } else {
407             let span = self.body.local_decls[local].source_info.span;
408             span_bug!(span, "{:?} not promotable, qualif_local shouldn't have been called", local);
409         }
410     }
411
412     // FIXME(eddyb) maybe cache this?
413     fn validate_local(&self, local: Local) -> Result<(), Unpromotable> {
414         if let TempState::Defined { location: loc, .. } = self.temps[local] {
415             let num_stmts = self.body[loc.block].statements.len();
416
417             if loc.statement_index < num_stmts {
418                 let statement = &self.body[loc.block].statements[loc.statement_index];
419                 match &statement.kind {
420                     StatementKind::Assign(box(_, rhs)) => self.validate_rvalue(rhs),
421                     _ => {
422                         span_bug!(statement.source_info.span, "{:?} is not an assignment",
423                                 statement);
424                     }
425                 }
426             } else {
427                 let terminator = self.body[loc.block].terminator();
428                 match &terminator.kind {
429                     TerminatorKind::Call { func, args, .. } => self.validate_call(func, args),
430                     kind => {
431                         span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
432                     }
433                 }
434             }
435         } else {
436             Err(Unpromotable)
437         }
438     }
439
440     fn validate_place(&self, place: PlaceRef<'_, 'tcx>) -> Result<(), Unpromotable> {
441         match place {
442             PlaceRef {
443                 base: PlaceBase::Local(local),
444                 projection: [],
445             } => self.validate_local(*local),
446             PlaceRef {
447                 base: PlaceBase::Static(box Static {
448                     kind: StaticKind::Promoted { .. },
449                     ..
450                 }),
451                 projection: [],
452             } => bug!("qualifying already promoted MIR"),
453             PlaceRef {
454                 base: PlaceBase::Static(box Static {
455                     kind: StaticKind::Static,
456                     def_id,
457                     ..
458                 }),
459                 projection: [],
460             } => {
461                 // Only allow statics (not consts) to refer to other statics.
462                 // FIXME(eddyb) does this matter at all for promotion?
463                 let is_static = self.const_kind.map_or(false, |k| k.is_static());
464                 if !is_static {
465                     return Err(Unpromotable);
466                 }
467
468                 let is_thread_local = self.tcx.has_attr(*def_id, sym::thread_local);
469                 if is_thread_local {
470                     return Err(Unpromotable);
471                 }
472
473                 Ok(())
474             }
475             PlaceRef {
476                 base: _,
477                 projection: [proj_base @ .., elem],
478             } => {
479                 match *elem {
480                     ProjectionElem::Deref |
481                     ProjectionElem::Downcast(..) => return Err(Unpromotable),
482
483                     ProjectionElem::ConstantIndex {..} |
484                     ProjectionElem::Subslice {..} => {}
485
486                     ProjectionElem::Index(local) => {
487                         self.validate_local(local)?;
488                     }
489
490                     ProjectionElem::Field(..) => {
491                         if self.const_kind.is_none() {
492                             let base_ty =
493                                 Place::ty_from(place.base, proj_base, self.body, self.tcx).ty;
494                             if let Some(def) = base_ty.ty_adt_def() {
495                                 // No promotion of union field accesses.
496                                 if def.is_union() {
497                                     return Err(Unpromotable);
498                                 }
499                             }
500                         }
501                     }
502                 }
503
504                 self.validate_place(PlaceRef {
505                     base: place.base,
506                     projection: proj_base,
507                 })
508             }
509         }
510     }
511
512     fn validate_operand(&self, operand: &Operand<'tcx>) -> Result<(), Unpromotable> {
513         match operand {
514             Operand::Copy(place) |
515             Operand::Move(place) => self.validate_place(place.as_ref()),
516
517             Operand::Constant(constant) => {
518                 if let ConstValue::Unevaluated(def_id, _) = constant.literal.val {
519                     if self.tcx.trait_of_item(def_id).is_some() {
520                         // Don't peek inside trait associated constants.
521                         // (see below what we do for other consts, for now)
522                     } else {
523                         // HACK(eddyb) ensure that errors propagate correctly.
524                         // FIXME(eddyb) remove this once the old promotion logic
525                         // is gone - we can always promote constants even if they
526                         // fail to pass const-checking, as compilation would've
527                         // errored independently and promotion can't change that.
528                         let (bits, _) = self.tcx.at(constant.span).mir_const_qualif(def_id);
529                         if bits == super::qualify_consts::QUALIF_ERROR_BIT {
530                             self.tcx.sess.delay_span_bug(
531                                 constant.span,
532                                 "promote_consts: MIR had errors",
533                             );
534                             return Err(Unpromotable);
535                         }
536                     }
537                 }
538
539                 Ok(())
540             }
541         }
542     }
543
544     fn validate_rvalue(&self, rvalue: &Rvalue<'tcx>) -> Result<(), Unpromotable> {
545         match *rvalue {
546             Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) if self.const_kind.is_none() => {
547                 let operand_ty = operand.ty(self.body, self.tcx);
548                 let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast");
549                 let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
550                 match (cast_in, cast_out) {
551                     (CastTy::Ptr(_), CastTy::Int(_)) |
552                     (CastTy::FnPtr, CastTy::Int(_)) => {
553                         // in normal functions, mark such casts as not promotable
554                         return Err(Unpromotable);
555                     }
556                     _ => {}
557                 }
558             }
559
560             Rvalue::BinaryOp(op, ref lhs, _) if self.const_kind.is_none() => {
561                 if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.body, self.tcx).kind {
562                     assert!(op == BinOp::Eq || op == BinOp::Ne ||
563                             op == BinOp::Le || op == BinOp::Lt ||
564                             op == BinOp::Ge || op == BinOp::Gt ||
565                             op == BinOp::Offset);
566
567                     // raw pointer operations are not allowed inside promoteds
568                     return Err(Unpromotable);
569                 }
570             }
571
572             Rvalue::NullaryOp(NullOp::Box, _) => return Err(Unpromotable),
573
574             _ => {}
575         }
576
577         match rvalue {
578             Rvalue::NullaryOp(..) => Ok(()),
579
580             Rvalue::Discriminant(place) |
581             Rvalue::Len(place) => self.validate_place(place.as_ref()),
582
583             Rvalue::Use(operand) |
584             Rvalue::Repeat(operand, _) |
585             Rvalue::UnaryOp(_, operand) |
586             Rvalue::Cast(_, operand, _) => self.validate_operand(operand),
587
588             Rvalue::BinaryOp(_, lhs, rhs) |
589             Rvalue::CheckedBinaryOp(_, lhs, rhs) => {
590                 self.validate_operand(lhs)?;
591                 self.validate_operand(rhs)
592             }
593
594             Rvalue::Ref(_, kind, place) => {
595                 if let BorrowKind::Mut { .. } = kind {
596                     let ty = place.ty(self.body, self.tcx).ty;
597
598                     // In theory, any zero-sized value could be borrowed
599                     // mutably without consequences. However, only &mut []
600                     // is allowed right now, and only in functions.
601                     if self.const_kind == Some(ConstKind::StaticMut) {
602                         // Inside a `static mut`, &mut [...] is also allowed.
603                         match ty.kind {
604                             ty::Array(..) | ty::Slice(_) => {}
605                             _ => return Err(Unpromotable),
606                         }
607                     } else if let ty::Array(_, len) = ty.kind {
608                         // FIXME(eddyb): We only return `Unpromotable` for `&mut []` inside a
609                         // const context which seems unnecessary given that this is merely a ZST.
610                         match len.try_eval_usize(self.tcx, self.param_env) {
611                             Some(0) if self.const_kind.is_none() => {},
612                             _ => return Err(Unpromotable),
613                         }
614                     } else {
615                         return Err(Unpromotable);
616                     }
617                 }
618
619                 // Special-case reborrows to be more like a copy of the reference.
620                 let mut place = place.as_ref();
621                 if let [proj_base @ .., ProjectionElem::Deref] = &place.projection {
622                     let base_ty =
623                         Place::ty_from(&place.base, proj_base, self.body, self.tcx).ty;
624                     if let ty::Ref(..) = base_ty.kind {
625                         place = PlaceRef {
626                             base: &place.base,
627                             projection: proj_base,
628                         };
629                     }
630                 }
631
632                 self.validate_place(place)?;
633
634                 // HACK(eddyb) this should compute the same thing as
635                 // `<HasMutInterior as Qualif>::in_projection` from
636                 // `check_consts::qualifs` but without recursion.
637                 let mut has_mut_interior = match place.base {
638                     PlaceBase::Local(local) => {
639                         self.qualif_local::<qualifs::HasMutInterior>(*local)
640                     }
641                     PlaceBase::Static(_) => false,
642                 };
643                 if has_mut_interior {
644                     let mut place_projection = place.projection;
645                     // FIXME(eddyb) use a forward loop instead of a reverse one.
646                     while let [proj_base @ .., elem] = place_projection {
647                         // FIXME(eddyb) this is probably excessive, with
648                         // the exception of `union` member accesses.
649                         let ty = Place::ty_from(place.base, proj_base, self.body, self.tcx)
650                             .projection_ty(self.tcx, elem)
651                             .ty;
652                         if ty.is_freeze(self.tcx, self.param_env, DUMMY_SP) {
653                             has_mut_interior = false;
654                             break;
655                         }
656
657                         place_projection = proj_base;
658                     }
659                 }
660                 if has_mut_interior {
661                     return Err(Unpromotable);
662                 }
663
664                 Ok(())
665             }
666
667             Rvalue::Aggregate(_, ref operands) => {
668                 for o in operands {
669                     self.validate_operand(o)?;
670                 }
671
672                 Ok(())
673             }
674         }
675     }
676
677     fn validate_call(
678         &self,
679         callee: &Operand<'tcx>,
680         args: &[Operand<'tcx>],
681     ) -> Result<(), Unpromotable> {
682         let fn_ty = callee.ty(self.body, self.tcx);
683
684         if !self.explicit && self.const_kind.is_none() {
685             if let ty::FnDef(def_id, _) = fn_ty.kind {
686                 // Never promote runtime `const fn` calls of
687                 // functions without `#[rustc_promotable]`.
688                 if !self.tcx.is_promotable_const_fn(def_id) {
689                     return Err(Unpromotable);
690                 }
691             }
692         }
693
694         let is_const_fn = match fn_ty.kind {
695             ty::FnDef(def_id, _) => {
696                 self.tcx.is_const_fn(def_id) ||
697                 self.tcx.is_unstable_const_fn(def_id).is_some() ||
698                 is_lang_panic_fn(self.tcx, self.def_id)
699             }
700             _ => false,
701         };
702         if !is_const_fn {
703             return Err(Unpromotable);
704         }
705
706         self.validate_operand(callee)?;
707         for arg in args {
708             self.validate_operand(arg)?;
709         }
710
711         Ok(())
712     }
713 }
714
715 // FIXME(eddyb) remove the differences for promotability in `static`, `const`, `const fn`.
716 pub fn validate_candidates(
717     tcx: TyCtxt<'tcx>,
718     body: &Body<'tcx>,
719     def_id: DefId,
720     temps: &IndexVec<Local, TempState>,
721     candidates: &[Candidate],
722 ) -> Vec<Candidate> {
723     let mut validator = Validator {
724         item: Item::new(tcx, def_id, body),
725         temps,
726         explicit: false,
727     };
728
729     candidates.iter().copied().filter(|&candidate| {
730         validator.explicit = match candidate {
731             Candidate::Ref(_) |
732             Candidate::Repeat(_) => false,
733             Candidate::Argument { .. } => true,
734         };
735
736         // FIXME(eddyb) also emit the errors for shuffle indices
737         // and `#[rustc_args_required_const]` arguments here.
738
739         validator.validate_candidate(candidate).is_ok()
740     }).collect()
741 }
742
743 struct Promoter<'a, 'tcx> {
744     tcx: TyCtxt<'tcx>,
745     source: &'a mut Body<'tcx>,
746     promoted: Body<'tcx>,
747     temps: &'a mut IndexVec<Local, TempState>,
748
749     /// If true, all nested temps are also kept in the
750     /// source MIR, not moved to the promoted MIR.
751     keep_original: bool,
752 }
753
754 impl<'a, 'tcx> Promoter<'a, 'tcx> {
755     fn new_block(&mut self) -> BasicBlock {
756         let span = self.promoted.span;
757         self.promoted.basic_blocks_mut().push(BasicBlockData {
758             statements: vec![],
759             terminator: Some(Terminator {
760                 source_info: SourceInfo {
761                     span,
762                     scope: OUTERMOST_SOURCE_SCOPE
763                 },
764                 kind: TerminatorKind::Return
765             }),
766             is_cleanup: false
767         })
768     }
769
770     fn assign(&mut self, dest: Local, rvalue: Rvalue<'tcx>, span: Span) {
771         let last = self.promoted.basic_blocks().last().unwrap();
772         let data = &mut self.promoted[last];
773         data.statements.push(Statement {
774             source_info: SourceInfo {
775                 span,
776                 scope: OUTERMOST_SOURCE_SCOPE
777             },
778             kind: StatementKind::Assign(box(Place::from(dest), rvalue))
779         });
780     }
781
782     fn is_temp_kind(&self, local: Local) -> bool {
783         self.source.local_kind(local) == LocalKind::Temp
784     }
785
786     /// Copies the initialization of this temp to the
787     /// promoted MIR, recursing through temps.
788     fn promote_temp(&mut self, temp: Local) -> Local {
789         let old_keep_original = self.keep_original;
790         let loc = match self.temps[temp] {
791             TempState::Defined { location, uses } if uses > 0 => {
792                 if uses > 1 {
793                     self.keep_original = true;
794                 }
795                 location
796             }
797             state =>  {
798                 span_bug!(self.promoted.span, "{:?} not promotable: {:?}",
799                           temp, state);
800             }
801         };
802         if !self.keep_original {
803             self.temps[temp] = TempState::PromotedOut;
804         }
805
806         let num_stmts = self.source[loc.block].statements.len();
807         let new_temp = self.promoted.local_decls.push(
808             LocalDecl::new_temp(self.source.local_decls[temp].ty,
809                                 self.source.local_decls[temp].source_info.span));
810
811         debug!("promote({:?} @ {:?}/{:?}, {:?})",
812                temp, loc, num_stmts, self.keep_original);
813
814         // First, take the Rvalue or Call out of the source MIR,
815         // or duplicate it, depending on keep_original.
816         if loc.statement_index < num_stmts {
817             let (mut rvalue, source_info) = {
818                 let statement = &mut self.source[loc.block].statements[loc.statement_index];
819                 let rhs = match statement.kind {
820                     StatementKind::Assign(box(_, ref mut rhs)) => rhs,
821                     _ => {
822                         span_bug!(statement.source_info.span, "{:?} is not an assignment",
823                                   statement);
824                     }
825                 };
826
827                 (if self.keep_original {
828                     rhs.clone()
829                 } else {
830                     let unit = Rvalue::Aggregate(box AggregateKind::Tuple, vec![]);
831                     mem::replace(rhs, unit)
832                 }, statement.source_info)
833             };
834
835             self.visit_rvalue(&mut rvalue, loc);
836             self.assign(new_temp, rvalue, source_info.span);
837         } else {
838             let terminator = if self.keep_original {
839                 self.source[loc.block].terminator().clone()
840             } else {
841                 let terminator = self.source[loc.block].terminator_mut();
842                 let target = match terminator.kind {
843                     TerminatorKind::Call { destination: Some((_, target)), .. } => target,
844                     ref kind => {
845                         span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
846                     }
847                 };
848                 Terminator {
849                     source_info: terminator.source_info,
850                     kind: mem::replace(&mut terminator.kind, TerminatorKind::Goto {
851                         target,
852                     })
853                 }
854             };
855
856             match terminator.kind {
857                 TerminatorKind::Call { mut func, mut args, from_hir_call, .. } => {
858                     self.visit_operand(&mut func, loc);
859                     for arg in &mut args {
860                         self.visit_operand(arg, loc);
861                     }
862
863                     let last = self.promoted.basic_blocks().last().unwrap();
864                     let new_target = self.new_block();
865
866                     *self.promoted[last].terminator_mut() = Terminator {
867                         kind: TerminatorKind::Call {
868                             func,
869                             args,
870                             cleanup: None,
871                             destination: Some(
872                                 (Place::from(new_temp), new_target)
873                             ),
874                             from_hir_call,
875                         },
876                         ..terminator
877                     };
878                 }
879                 ref kind => {
880                     span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
881                 }
882             };
883         };
884
885         self.keep_original = old_keep_original;
886         new_temp
887     }
888
889     fn promote_candidate(
890         mut self,
891         def_id: DefId,
892         candidate: Candidate,
893         next_promoted_id: usize,
894     ) -> Option<Body<'tcx>> {
895         let mut operand = {
896             let promoted = &mut self.promoted;
897             let promoted_id = Promoted::new(next_promoted_id);
898             let tcx = self.tcx;
899             let mut promoted_place = |ty, span| {
900                 promoted.span = span;
901                 promoted.local_decls[RETURN_PLACE] = LocalDecl::new_return_place(ty, span);
902                 Place {
903                     base: PlaceBase::Static(box Static {
904                         kind:
905                             StaticKind::Promoted(
906                                 promoted_id,
907                                 InternalSubsts::identity_for_item(tcx, def_id),
908                             ),
909                         ty,
910                         def_id,
911                     }),
912                     projection: List::empty(),
913                 }
914             };
915             let (blocks, local_decls) = self.source.basic_blocks_and_local_decls_mut();
916             match candidate {
917                 Candidate::Ref(loc) => {
918                     let ref mut statement = blocks[loc.block].statements[loc.statement_index];
919                     match statement.kind {
920                         StatementKind::Assign(box(_, Rvalue::Ref(_, _, ref mut place))) => {
921                             // Use the underlying local for this (necessarily interior) borrow.
922                             let ty = place.base.ty(local_decls).ty;
923                             let span = statement.source_info.span;
924
925                             Operand::Move(Place {
926                                 base: mem::replace(
927                                     &mut place.base,
928                                     promoted_place(ty, span).base,
929                                 ),
930                                 projection: List::empty(),
931                             })
932                         }
933                         _ => bug!()
934                     }
935                 }
936                 Candidate::Repeat(loc) => {
937                     let ref mut statement = blocks[loc.block].statements[loc.statement_index];
938                     match statement.kind {
939                         StatementKind::Assign(box(_, Rvalue::Repeat(ref mut operand, _))) => {
940                             let ty = operand.ty(local_decls, self.tcx);
941                             let span = statement.source_info.span;
942                             mem::replace(
943                                 operand,
944                                 Operand::Copy(promoted_place(ty, span))
945                             )
946                         }
947                         _ => bug!()
948                     }
949                 },
950                 Candidate::Argument { bb, index } => {
951                     let terminator = blocks[bb].terminator_mut();
952                     match terminator.kind {
953                         TerminatorKind::Call { ref mut args, .. } => {
954                             let ty = args[index].ty(local_decls, self.tcx);
955                             let span = terminator.source_info.span;
956                             let operand = Operand::Copy(promoted_place(ty, span));
957                             mem::replace(&mut args[index], operand)
958                         }
959                         // We expected a `TerminatorKind::Call` for which we'd like to promote an
960                         // argument. `qualify_consts` saw a `TerminatorKind::Call` here, but
961                         // we are seeing a `Goto`. That means that the `promote_temps` method
962                         // already promoted this call away entirely. This case occurs when calling
963                         // a function requiring a constant argument and as that constant value
964                         // providing a value whose computation contains another call to a function
965                         // requiring a constant argument.
966                         TerminatorKind::Goto { .. } => return None,
967                         _ => bug!()
968                     }
969                 }
970             }
971         };
972
973         assert_eq!(self.new_block(), START_BLOCK);
974         self.visit_operand(&mut operand, Location {
975             block: BasicBlock::new(0),
976             statement_index: usize::MAX
977         });
978
979         let span = self.promoted.span;
980         self.assign(RETURN_PLACE, Rvalue::Use(operand), span);
981         Some(self.promoted)
982     }
983 }
984
985 /// Replaces all temporaries with their promoted counterparts.
986 impl<'a, 'tcx> MutVisitor<'tcx> for Promoter<'a, 'tcx> {
987     fn tcx(&self) -> TyCtxt<'tcx> {
988         self.tcx
989     }
990
991     fn visit_local(&mut self,
992                    local: &mut Local,
993                    _: PlaceContext,
994                    _: Location) {
995         if self.is_temp_kind(*local) {
996             *local = self.promote_temp(*local);
997         }
998     }
999
1000     fn process_projection_elem(
1001         &mut self,
1002         elem: &PlaceElem<'tcx>,
1003     ) -> Option<PlaceElem<'tcx>> {
1004         match elem {
1005             PlaceElem::Index(local) if self.is_temp_kind(*local) => {
1006                 Some(PlaceElem::Index(self.promote_temp(*local)))
1007             }
1008             _ => None,
1009         }
1010     }
1011 }
1012
1013 pub fn promote_candidates<'tcx>(
1014     def_id: DefId,
1015     body: &mut Body<'tcx>,
1016     tcx: TyCtxt<'tcx>,
1017     mut temps: IndexVec<Local, TempState>,
1018     candidates: Vec<Candidate>,
1019 ) -> IndexVec<Promoted, Body<'tcx>> {
1020     // Visit candidates in reverse, in case they're nested.
1021     debug!("promote_candidates({:?})", candidates);
1022
1023     let mut promotions = IndexVec::new();
1024
1025     for candidate in candidates.into_iter().rev() {
1026         match candidate {
1027             Candidate::Repeat(Location { block, statement_index }) |
1028             Candidate::Ref(Location { block, statement_index }) => {
1029                 match &body[block].statements[statement_index].kind {
1030                     StatementKind::Assign(box(place, _)) => {
1031                         if let Some(local) = place.as_local() {
1032                             if temps[local] == TempState::PromotedOut {
1033                                 // Already promoted.
1034                                 continue;
1035                             }
1036                         }
1037                     }
1038                     _ => {}
1039                 }
1040             }
1041             Candidate::Argument { .. } => {}
1042         }
1043
1044
1045         // Declare return place local so that `mir::Body::new` doesn't complain.
1046         let initial_locals = iter::once(
1047             LocalDecl::new_return_place(tcx.types.never, body.span)
1048         ).collect();
1049
1050         let promoter = Promoter {
1051             promoted: Body::new(
1052                 IndexVec::new(),
1053                 // FIXME: maybe try to filter this to avoid blowing up
1054                 // memory usage?
1055                 body.source_scopes.clone(),
1056                 body.source_scope_local_data.clone(),
1057                 None,
1058                 initial_locals,
1059                 IndexVec::new(),
1060                 0,
1061                 vec![],
1062                 body.span,
1063                 vec![],
1064             ),
1065             tcx,
1066             source: body,
1067             temps: &mut temps,
1068             keep_original: false
1069         };
1070
1071         //FIXME(oli-obk): having a `maybe_push()` method on `IndexVec` might be nice
1072         if let Some(promoted) = promoter.promote_candidate(def_id, candidate, promotions.len()) {
1073             promotions.push(promoted);
1074         }
1075     }
1076
1077     // Eliminate assignments to, and drops of promoted temps.
1078     let promoted = |index: Local| temps[index] == TempState::PromotedOut;
1079     for block in body.basic_blocks_mut() {
1080         block.statements.retain(|statement| {
1081             match &statement.kind {
1082                 StatementKind::Assign(box(place, _)) => {
1083                     if let Some(index) = place.as_local() {
1084                         !promoted(index)
1085                     } else {
1086                         true
1087                     }
1088                 }
1089                 StatementKind::StorageLive(index) |
1090                 StatementKind::StorageDead(index) => {
1091                     !promoted(*index)
1092                 }
1093                 _ => true
1094             }
1095         });
1096         let terminator = block.terminator_mut();
1097         match &terminator.kind {
1098             TerminatorKind::Drop { location: place, target, .. } => {
1099                 if let Some(index) = place.as_local() {
1100                     if promoted(index) {
1101                         terminator.kind = TerminatorKind::Goto {
1102                             target: *target,
1103                         };
1104                     }
1105                 }
1106             }
1107             _ => {}
1108         }
1109     }
1110
1111     promotions
1112 }