]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir/src/transform/promote_consts.rs
Rollup merge of #81764 - jyn514:lint-links, r=GuillaumeGomez
[rust.git] / compiler / rustc_mir / src / 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_ast::LitKind;
16 use rustc_hir as hir;
17 use rustc_hir::def_id::DefId;
18 use rustc_middle::mir::traversal::ReversePostorder;
19 use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
20 use rustc_middle::mir::*;
21 use rustc_middle::ty::cast::CastTy;
22 use rustc_middle::ty::subst::InternalSubsts;
23 use rustc_middle::ty::{self, List, TyCtxt, TypeFoldable};
24 use rustc_span::symbol::sym;
25 use rustc_span::Span;
26
27 use rustc_index::vec::{Idx, IndexVec};
28 use rustc_target::spec::abi::Abi;
29
30 use std::cell::Cell;
31 use std::{cmp, iter, mem};
32
33 use crate::const_eval::{is_const_fn, is_unstable_const_fn};
34 use crate::transform::check_consts::{is_lang_panic_fn, qualifs, ConstCx};
35 use crate::transform::MirPass;
36
37 /// A `MirPass` for promotion.
38 ///
39 /// Promotion is the extraction of promotable temps into separate MIR bodies. This pass also emits
40 /// errors when promotion of `#[rustc_args_required_const]` arguments fails.
41 ///
42 /// After this pass is run, `promoted_fragments` will hold the MIR body corresponding to each
43 /// newly created `Constant`.
44 #[derive(Default)]
45 pub struct PromoteTemps<'tcx> {
46     pub promoted_fragments: Cell<IndexVec<Promoted, Body<'tcx>>>,
47 }
48
49 impl<'tcx> MirPass<'tcx> for PromoteTemps<'tcx> {
50     fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
51         // There's not really any point in promoting errorful MIR.
52         //
53         // This does not include MIR that failed const-checking, which we still try to promote.
54         if body.return_ty().references_error() {
55             tcx.sess.delay_span_bug(body.span, "PromoteTemps: MIR had errors");
56             return;
57         }
58
59         if body.source.promoted.is_some() {
60             return;
61         }
62
63         let mut rpo = traversal::reverse_postorder(body);
64         let ccx = ConstCx::new(tcx, body);
65         let (temps, all_candidates) = collect_temps_and_candidates(&ccx, &mut rpo);
66
67         let promotable_candidates = validate_candidates(&ccx, &temps, &all_candidates);
68
69         let promoted = promote_candidates(body, tcx, temps, promotable_candidates);
70         self.promoted_fragments.set(promoted);
71     }
72 }
73
74 /// State of a temporary during collection and promotion.
75 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
76 pub enum TempState {
77     /// No references to this temp.
78     Undefined,
79     /// One direct assignment and any number of direct uses.
80     /// A borrow of this temp is promotable if the assigned
81     /// value is qualified as constant.
82     Defined { location: Location, uses: usize },
83     /// Any other combination of assignments/uses.
84     Unpromotable,
85     /// This temp was part of an rvalue which got extracted
86     /// during promotion and needs cleanup.
87     PromotedOut,
88 }
89
90 impl TempState {
91     pub fn is_promotable(&self) -> bool {
92         debug!("is_promotable: self={:?}", self);
93         matches!(self, TempState::Defined { .. })
94     }
95 }
96
97 /// A "root candidate" for promotion, which will become the
98 /// returned value in a promoted MIR, unless it's a subset
99 /// of a larger candidate.
100 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
101 pub enum Candidate {
102     /// Borrow of a constant temporary, candidate for lifetime extension.
103     Ref(Location),
104
105     /// Currently applied to function calls where the callee has the unstable
106     /// `#[rustc_args_required_const]` attribute as well as the SIMD shuffle
107     /// intrinsic. The intrinsic requires the arguments are indeed constant and
108     /// the attribute currently provides the semantic requirement that arguments
109     /// must be constant.
110     Argument { bb: BasicBlock, index: usize },
111 }
112
113 impl Candidate {
114     /// Returns `true` if we should use the "explicit" rules for promotability for this `Candidate`.
115     fn forces_explicit_promotion(&self) -> bool {
116         match self {
117             Candidate::Ref(_) => false,
118             Candidate::Argument { .. } => true,
119         }
120     }
121
122     fn source_info(&self, body: &Body<'_>) -> SourceInfo {
123         match self {
124             Candidate::Ref(location) => *body.source_info(*location),
125             Candidate::Argument { bb, .. } => *body.source_info(body.terminator_loc(*bb)),
126         }
127     }
128 }
129
130 fn args_required_const(tcx: TyCtxt<'_>, def_id: DefId) -> Option<Vec<usize>> {
131     let attrs = tcx.get_attrs(def_id);
132     let attr = attrs.iter().find(|a| tcx.sess.check_name(a, sym::rustc_args_required_const))?;
133     let mut ret = vec![];
134     for meta in attr.meta_item_list()? {
135         match meta.literal()?.kind {
136             LitKind::Int(a, _) => {
137                 ret.push(a as usize);
138             }
139             _ => bug!("invalid arg index"),
140         }
141     }
142     Some(ret)
143 }
144
145 struct Collector<'a, 'tcx> {
146     ccx: &'a ConstCx<'a, 'tcx>,
147     temps: IndexVec<Local, TempState>,
148     candidates: Vec<Candidate>,
149 }
150
151 impl<'tcx> Visitor<'tcx> for Collector<'_, 'tcx> {
152     fn visit_local(&mut self, &index: &Local, context: PlaceContext, location: Location) {
153         debug!("visit_local: index={:?} context={:?} location={:?}", index, context, location);
154         // We're only interested in temporaries and the return place
155         match self.ccx.body.local_kind(index) {
156             LocalKind::Temp | LocalKind::ReturnPointer => {}
157             LocalKind::Arg | LocalKind::Var => return,
158         }
159
160         // Ignore drops, if the temp gets promoted,
161         // then it's constant and thus drop is noop.
162         // Non-uses are also irrelevant.
163         if context.is_drop() || !context.is_use() {
164             debug!(
165                 "visit_local: context.is_drop={:?} context.is_use={:?}",
166                 context.is_drop(),
167                 context.is_use(),
168             );
169             return;
170         }
171
172         let temp = &mut self.temps[index];
173         debug!("visit_local: temp={:?}", temp);
174         if *temp == TempState::Undefined {
175             match context {
176                 PlaceContext::MutatingUse(MutatingUseContext::Store)
177                 | PlaceContext::MutatingUse(MutatingUseContext::Call) => {
178                     *temp = TempState::Defined { location, uses: 0 };
179                     return;
180                 }
181                 _ => { /* mark as unpromotable below */ }
182             }
183         } else if let TempState::Defined { ref mut uses, .. } = *temp {
184             // We always allow borrows, even mutable ones, as we need
185             // to promote mutable borrows of some ZSTs e.g., `&mut []`.
186             let allowed_use = match context {
187                 PlaceContext::MutatingUse(MutatingUseContext::Borrow)
188                 | PlaceContext::NonMutatingUse(_) => true,
189                 PlaceContext::MutatingUse(_) | PlaceContext::NonUse(_) => false,
190             };
191             debug!("visit_local: allowed_use={:?}", allowed_use);
192             if allowed_use {
193                 *uses += 1;
194                 return;
195             }
196             /* mark as unpromotable below */
197         }
198         *temp = TempState::Unpromotable;
199     }
200
201     fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
202         self.super_rvalue(rvalue, location);
203
204         match *rvalue {
205             Rvalue::Ref(..) => {
206                 self.candidates.push(Candidate::Ref(location));
207             }
208             _ => {}
209         }
210     }
211
212     fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
213         self.super_terminator(terminator, location);
214
215         if let TerminatorKind::Call { ref func, .. } = terminator.kind {
216             if let ty::FnDef(def_id, _) = *func.ty(self.ccx.body, self.ccx.tcx).kind() {
217                 let fn_sig = self.ccx.tcx.fn_sig(def_id);
218                 if let Abi::RustIntrinsic | Abi::PlatformIntrinsic = fn_sig.abi() {
219                     let name = self.ccx.tcx.item_name(def_id);
220                     // FIXME(eddyb) use `#[rustc_args_required_const(2)]` for shuffles.
221                     if name.as_str().starts_with("simd_shuffle") {
222                         self.candidates.push(Candidate::Argument { bb: location.block, index: 2 });
223
224                         return; // Don't double count `simd_shuffle` candidates
225                     }
226                 }
227
228                 if let Some(constant_args) = args_required_const(self.ccx.tcx, def_id) {
229                     for index in constant_args {
230                         self.candidates.push(Candidate::Argument { bb: location.block, index });
231                     }
232                 }
233             }
234         }
235     }
236 }
237
238 pub fn collect_temps_and_candidates(
239     ccx: &ConstCx<'mir, 'tcx>,
240     rpo: &mut ReversePostorder<'_, 'tcx>,
241 ) -> (IndexVec<Local, TempState>, Vec<Candidate>) {
242     let mut collector = Collector {
243         temps: IndexVec::from_elem(TempState::Undefined, &ccx.body.local_decls),
244         candidates: vec![],
245         ccx,
246     };
247     for (bb, data) in rpo {
248         collector.visit_basic_block_data(bb, data);
249     }
250     (collector.temps, collector.candidates)
251 }
252
253 /// Checks whether locals that appear in a promotion context (`Candidate`) are actually promotable.
254 ///
255 /// This wraps an `Item`, and has access to all fields of that `Item` via `Deref` coercion.
256 struct Validator<'a, 'tcx> {
257     ccx: &'a ConstCx<'a, 'tcx>,
258     temps: &'a IndexVec<Local, TempState>,
259
260     /// Explicit promotion happens e.g. for constant arguments declared via
261     /// `rustc_args_required_const`.
262     /// Implicit promotion has almost the same rules, except that disallows `const fn`
263     /// except for those marked `#[rustc_promotable]`. This is to avoid changing
264     /// a legitimate run-time operation into a failing compile-time operation
265     /// e.g. due to addresses being compared inside the function.
266     explicit: bool,
267 }
268
269 impl std::ops::Deref for Validator<'a, 'tcx> {
270     type Target = ConstCx<'a, 'tcx>;
271
272     fn deref(&self) -> &Self::Target {
273         &self.ccx
274     }
275 }
276
277 struct Unpromotable;
278
279 impl<'tcx> Validator<'_, 'tcx> {
280     fn validate_candidate(&self, candidate: Candidate) -> Result<(), Unpromotable> {
281         match candidate {
282             Candidate::Ref(loc) => {
283                 assert!(!self.explicit);
284
285                 let statement = &self.body[loc.block].statements[loc.statement_index];
286                 match &statement.kind {
287                     StatementKind::Assign(box (_, Rvalue::Ref(_, kind, place))) => {
288                         // We can only promote interior borrows of promotable temps (non-temps
289                         // don't get promoted anyway).
290                         self.validate_local(place.local)?;
291
292                         // The reference operation itself must be promotable.
293                         // (Needs to come after `validate_local` to avoid ICEs.)
294                         self.validate_ref(*kind, place)?;
295
296                         // We do not check all the projections (they do not get promoted anyway),
297                         // but we do stay away from promoting anything involving a dereference.
298                         if place.projection.contains(&ProjectionElem::Deref) {
299                             return Err(Unpromotable);
300                         }
301
302                         // We cannot promote things that need dropping, since the promoted value
303                         // would not get dropped.
304                         if self.qualif_local::<qualifs::NeedsDrop>(place.local) {
305                             return Err(Unpromotable);
306                         }
307
308                         Ok(())
309                     }
310                     _ => bug!(),
311                 }
312             }
313             Candidate::Argument { bb, index } => {
314                 assert!(self.explicit);
315
316                 let terminator = self.body[bb].terminator();
317                 match &terminator.kind {
318                     TerminatorKind::Call { args, .. } => self.validate_operand(&args[index]),
319                     _ => bug!(),
320                 }
321             }
322         }
323     }
324
325     // FIXME(eddyb) maybe cache this?
326     fn qualif_local<Q: qualifs::Qualif>(&self, local: Local) -> bool {
327         if let TempState::Defined { location: loc, .. } = self.temps[local] {
328             let num_stmts = self.body[loc.block].statements.len();
329
330             if loc.statement_index < num_stmts {
331                 let statement = &self.body[loc.block].statements[loc.statement_index];
332                 match &statement.kind {
333                     StatementKind::Assign(box (_, rhs)) => qualifs::in_rvalue::<Q, _>(
334                         &self.ccx,
335                         &mut |l| self.qualif_local::<Q>(l),
336                         rhs,
337                     ),
338                     _ => {
339                         span_bug!(
340                             statement.source_info.span,
341                             "{:?} is not an assignment",
342                             statement
343                         );
344                     }
345                 }
346             } else {
347                 let terminator = self.body[loc.block].terminator();
348                 match &terminator.kind {
349                     TerminatorKind::Call { .. } => {
350                         let return_ty = self.body.local_decls[local].ty;
351                         Q::in_any_value_of_ty(&self.ccx, return_ty)
352                     }
353                     kind => {
354                         span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
355                     }
356                 }
357             }
358         } else {
359             let span = self.body.local_decls[local].source_info.span;
360             span_bug!(span, "{:?} not promotable, qualif_local shouldn't have been called", local);
361         }
362     }
363
364     // FIXME(eddyb) maybe cache this?
365     fn validate_local(&self, local: Local) -> Result<(), Unpromotable> {
366         if let TempState::Defined { location: loc, .. } = self.temps[local] {
367             let block = &self.body[loc.block];
368             let num_stmts = block.statements.len();
369
370             if loc.statement_index < num_stmts {
371                 let statement = &block.statements[loc.statement_index];
372                 match &statement.kind {
373                     StatementKind::Assign(box (_, rhs)) => self.validate_rvalue(rhs),
374                     _ => {
375                         span_bug!(
376                             statement.source_info.span,
377                             "{:?} is not an assignment",
378                             statement
379                         );
380                     }
381                 }
382             } else {
383                 let terminator = block.terminator();
384                 match &terminator.kind {
385                     TerminatorKind::Call { func, args, .. } => self.validate_call(func, args),
386                     TerminatorKind::Yield { .. } => Err(Unpromotable),
387                     kind => {
388                         span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
389                     }
390                 }
391             }
392         } else {
393             Err(Unpromotable)
394         }
395     }
396
397     fn validate_place(&self, place: PlaceRef<'tcx>) -> Result<(), Unpromotable> {
398         match place.last_projection() {
399             None => self.validate_local(place.local),
400             Some((place_base, elem)) => {
401                 // Validate topmost projection, then recurse.
402                 match elem {
403                     ProjectionElem::Deref => {
404                         let mut promotable = false;
405                         // We need to make sure this is a `Deref` of a local with no further projections.
406                         // Discussion can be found at
407                         // https://github.com/rust-lang/rust/pull/74945#discussion_r463063247
408                         if let Some(local) = place_base.as_local() {
409                             // This is a special treatment for cases like *&STATIC where STATIC is a
410                             // global static variable.
411                             // This pattern is generated only when global static variables are directly
412                             // accessed and is qualified for promotion safely.
413                             if let TempState::Defined { location, .. } = self.temps[local] {
414                                 let def_stmt = self.body[location.block]
415                                     .statements
416                                     .get(location.statement_index);
417                                 if let Some(Statement {
418                                     kind:
419                                         StatementKind::Assign(box (
420                                             _,
421                                             Rvalue::Use(Operand::Constant(c)),
422                                         )),
423                                     ..
424                                 }) = def_stmt
425                                 {
426                                     if let Some(did) = c.check_static_ptr(self.tcx) {
427                                         // Evaluating a promoted may not read statics except if it got
428                                         // promoted from a static (this is a CTFE check). So we
429                                         // can only promote static accesses inside statics.
430                                         if let Some(hir::ConstContext::Static(..)) = self.const_kind
431                                         {
432                                             if !self.tcx.is_thread_local_static(did) {
433                                                 promotable = true;
434                                             }
435                                         }
436                                     }
437                                 }
438                             }
439                         }
440                         if !promotable {
441                             return Err(Unpromotable);
442                         }
443                     }
444                     ProjectionElem::Downcast(..) => {
445                         return Err(Unpromotable);
446                     }
447
448                     ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => {}
449
450                     ProjectionElem::Index(local) => {
451                         if !self.explicit {
452                             let mut promotable = false;
453                             // Only accept if we can predict the index and are indexing an array.
454                             let val = if let TempState::Defined { location: loc, .. } =
455                                 self.temps[local]
456                             {
457                                 let block = &self.body[loc.block];
458                                 if loc.statement_index < block.statements.len() {
459                                     let statement = &block.statements[loc.statement_index];
460                                     match &statement.kind {
461                                         StatementKind::Assign(box (
462                                             _,
463                                             Rvalue::Use(Operand::Constant(c)),
464                                         )) => c.literal.try_eval_usize(self.tcx, self.param_env),
465                                         _ => None,
466                                     }
467                                 } else {
468                                     None
469                                 }
470                             } else {
471                                 None
472                             };
473                             if let Some(idx) = val {
474                                 // Determine the type of the thing we are indexing.
475                                 let ty = place_base.ty(self.body, self.tcx).ty;
476                                 match ty.kind() {
477                                     ty::Array(_, len) => {
478                                         // It's an array; determine its length.
479                                         if let Some(len) =
480                                             len.try_eval_usize(self.tcx, self.param_env)
481                                         {
482                                             // If the index is in-bounds, go ahead.
483                                             if idx < len {
484                                                 promotable = true;
485                                             }
486                                         }
487                                     }
488                                     _ => {}
489                                 }
490                             }
491                             if !promotable {
492                                 return Err(Unpromotable);
493                             }
494                         }
495                         self.validate_local(local)?;
496                     }
497
498                     ProjectionElem::Field(..) => {
499                         let base_ty = place_base.ty(self.body, self.tcx).ty;
500                         if let Some(def) = base_ty.ty_adt_def() {
501                             // No promotion of union field accesses.
502                             if def.is_union() {
503                                 return Err(Unpromotable);
504                             }
505                         }
506                     }
507                 }
508
509                 self.validate_place(place_base)
510             }
511         }
512     }
513
514     fn validate_operand(&self, operand: &Operand<'tcx>) -> Result<(), Unpromotable> {
515         match operand {
516             Operand::Copy(place) | Operand::Move(place) => self.validate_place(place.as_ref()),
517
518             // The qualifs for a constant (e.g. `HasMutInterior`) are checked in
519             // `validate_rvalue` upon access.
520             Operand::Constant(c) => {
521                 if let Some(def_id) = c.check_static_ptr(self.tcx) {
522                     // Only allow statics (not consts) to refer to other statics.
523                     // FIXME(eddyb) does this matter at all for promotion?
524                     // FIXME(RalfJung) it makes little sense to not promote this in `fn`/`const fn`,
525                     // and in `const` this cannot occur anyway. The only concern is that we might
526                     // promote even `let x = &STATIC` which would be useless, but this applies to
527                     // promotion inside statics as well.
528                     let is_static = matches!(self.const_kind, Some(hir::ConstContext::Static(_)));
529                     if !is_static {
530                         return Err(Unpromotable);
531                     }
532
533                     let is_thread_local = self.tcx.is_thread_local_static(def_id);
534                     if is_thread_local {
535                         return Err(Unpromotable);
536                     }
537                 }
538
539                 Ok(())
540             }
541         }
542     }
543
544     fn validate_ref(&self, kind: BorrowKind, place: &Place<'tcx>) -> Result<(), Unpromotable> {
545         match kind {
546             // Reject these borrow types just to be safe.
547             // FIXME(RalfJung): could we allow them? Should we? No point in it until we have a usecase.
548             BorrowKind::Shallow | BorrowKind::Unique => return Err(Unpromotable),
549
550             BorrowKind::Shared => {
551                 let has_mut_interior = self.qualif_local::<qualifs::HasMutInterior>(place.local);
552                 if has_mut_interior {
553                     return Err(Unpromotable);
554                 }
555             }
556
557             BorrowKind::Mut { .. } => {
558                 let ty = place.ty(self.body, self.tcx).ty;
559
560                 // In theory, any zero-sized value could be borrowed
561                 // mutably without consequences. However, only &mut []
562                 // is allowed right now.
563                 if let ty::Array(_, len) = ty.kind() {
564                     match len.try_eval_usize(self.tcx, self.param_env) {
565                         Some(0) => {}
566                         _ => return Err(Unpromotable),
567                     }
568                 } else {
569                     return Err(Unpromotable);
570                 }
571             }
572         }
573
574         Ok(())
575     }
576
577     fn validate_rvalue(&self, rvalue: &Rvalue<'tcx>) -> Result<(), Unpromotable> {
578         match rvalue {
579             Rvalue::Use(operand) | Rvalue::Repeat(operand, _) => {
580                 self.validate_operand(operand)?;
581             }
582
583             Rvalue::Discriminant(place) | Rvalue::Len(place) => {
584                 self.validate_place(place.as_ref())?
585             }
586
587             Rvalue::ThreadLocalRef(_) => return Err(Unpromotable),
588
589             Rvalue::Cast(kind, operand, cast_ty) => {
590                 if matches!(kind, CastKind::Misc) {
591                     let operand_ty = operand.ty(self.body, self.tcx);
592                     let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast");
593                     let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
594                     if let (CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) = (cast_in, cast_out) {
595                         // ptr-to-int casts are not possible in consts and thus not promotable
596                         return Err(Unpromotable);
597                     }
598                     // int-to-ptr casts are fine, they just use the integer value at pointer type.
599                 }
600
601                 self.validate_operand(operand)?;
602             }
603
604             Rvalue::NullaryOp(op, _) => match op {
605                 NullOp::Box => return Err(Unpromotable),
606                 NullOp::SizeOf => {}
607             },
608
609             Rvalue::UnaryOp(op, operand) => {
610                 match op {
611                     // These operations can never fail.
612                     UnOp::Neg | UnOp::Not => {}
613                 }
614
615                 self.validate_operand(operand)?;
616             }
617
618             Rvalue::BinaryOp(op, box (lhs, rhs)) | Rvalue::CheckedBinaryOp(op, box (lhs, rhs)) => {
619                 let op = *op;
620                 let lhs_ty = lhs.ty(self.body, self.tcx);
621
622                 if let ty::RawPtr(_) | ty::FnPtr(..) = lhs_ty.kind() {
623                     // Raw and fn pointer operations are not allowed inside consts and thus not promotable.
624                     assert!(matches!(
625                         op,
626                         BinOp::Eq
627                             | BinOp::Ne
628                             | BinOp::Le
629                             | BinOp::Lt
630                             | BinOp::Ge
631                             | BinOp::Gt
632                             | BinOp::Offset
633                     ));
634                     return Err(Unpromotable);
635                 }
636
637                 match op {
638                     BinOp::Div | BinOp::Rem => {
639                         if !self.explicit && lhs_ty.is_integral() {
640                             // Integer division: the RHS must be a non-zero const.
641                             let const_val = match rhs {
642                                 Operand::Constant(c) => {
643                                     c.literal.try_eval_bits(self.tcx, self.param_env, lhs_ty)
644                                 }
645                                 _ => None,
646                             };
647                             match const_val {
648                                 Some(x) if x != 0 => {}        // okay
649                                 _ => return Err(Unpromotable), // value not known or 0 -- not okay
650                             }
651                         }
652                     }
653                     // The remaining operations can never fail.
654                     BinOp::Eq
655                     | BinOp::Ne
656                     | BinOp::Le
657                     | BinOp::Lt
658                     | BinOp::Ge
659                     | BinOp::Gt
660                     | BinOp::Offset
661                     | BinOp::Add
662                     | BinOp::Sub
663                     | BinOp::Mul
664                     | BinOp::BitXor
665                     | BinOp::BitAnd
666                     | BinOp::BitOr
667                     | BinOp::Shl
668                     | BinOp::Shr => {}
669                 }
670
671                 self.validate_operand(lhs)?;
672                 self.validate_operand(rhs)?;
673             }
674
675             Rvalue::AddressOf(_, place) => {
676                 // We accept `&raw *`, i.e., raw reborrows -- creating a raw pointer is
677                 // no problem, only using it is.
678                 if let Some((place_base, ProjectionElem::Deref)) = place.as_ref().last_projection()
679                 {
680                     let base_ty = place_base.ty(self.body, self.tcx).ty;
681                     if let ty::Ref(..) = base_ty.kind() {
682                         return self.validate_place(place_base);
683                     }
684                 }
685                 return Err(Unpromotable);
686             }
687
688             Rvalue::Ref(_, kind, place) => {
689                 // Special-case reborrows to be more like a copy of the reference.
690                 let mut place_simplified = place.as_ref();
691                 if let Some((place_base, ProjectionElem::Deref)) =
692                     place_simplified.last_projection()
693                 {
694                     let base_ty = place_base.ty(self.body, self.tcx).ty;
695                     if let ty::Ref(..) = base_ty.kind() {
696                         place_simplified = place_base;
697                     }
698                 }
699
700                 self.validate_place(place_simplified)?;
701
702                 // Check that the reference is fine (using the original place!).
703                 // (Needs to come after `validate_place` to avoid ICEs.)
704                 self.validate_ref(*kind, place)?;
705             }
706
707             Rvalue::Aggregate(_, operands) => {
708                 for o in operands {
709                     self.validate_operand(o)?;
710                 }
711             }
712         }
713
714         Ok(())
715     }
716
717     fn validate_call(
718         &self,
719         callee: &Operand<'tcx>,
720         args: &[Operand<'tcx>],
721     ) -> Result<(), Unpromotable> {
722         let fn_ty = callee.ty(self.body, self.tcx);
723
724         // When doing explicit promotion and inside const/static items, we promote all (eligible) function calls.
725         // Everywhere else, we require `#[rustc_promotable]` on the callee.
726         let promote_all_const_fn = self.explicit
727             || matches!(
728                 self.const_kind,
729                 Some(hir::ConstContext::Static(_) | hir::ConstContext::Const)
730             );
731         if !promote_all_const_fn {
732             if let ty::FnDef(def_id, _) = *fn_ty.kind() {
733                 // Never promote runtime `const fn` calls of
734                 // functions without `#[rustc_promotable]`.
735                 if !self.tcx.is_promotable_const_fn(def_id) {
736                     return Err(Unpromotable);
737                 }
738             }
739         }
740
741         let is_const_fn = match *fn_ty.kind() {
742             ty::FnDef(def_id, _) => {
743                 is_const_fn(self.tcx, def_id)
744                     || is_unstable_const_fn(self.tcx, def_id).is_some()
745                     || is_lang_panic_fn(self.tcx, def_id)
746             }
747             _ => false,
748         };
749         if !is_const_fn {
750             return Err(Unpromotable);
751         }
752
753         self.validate_operand(callee)?;
754         for arg in args {
755             self.validate_operand(arg)?;
756         }
757
758         Ok(())
759     }
760 }
761
762 // FIXME(eddyb) remove the differences for promotability in `static`, `const`, `const fn`.
763 pub fn validate_candidates(
764     ccx: &ConstCx<'_, '_>,
765     temps: &IndexVec<Local, TempState>,
766     candidates: &[Candidate],
767 ) -> Vec<Candidate> {
768     let mut validator = Validator { ccx, temps, explicit: false };
769
770     candidates
771         .iter()
772         .copied()
773         .filter(|&candidate| {
774             validator.explicit = candidate.forces_explicit_promotion();
775
776             // FIXME(eddyb) also emit the errors for shuffle indices
777             // and `#[rustc_args_required_const]` arguments here.
778
779             let is_promotable = validator.validate_candidate(candidate).is_ok();
780
781             // If we use explicit validation, we carry the risk of turning a legitimate run-time
782             // operation into a failing compile-time operation. Make sure that does not happen
783             // by asserting that there is no possible run-time behavior here in case promotion
784             // fails.
785             if validator.explicit && !is_promotable {
786                 ccx.tcx.sess.delay_span_bug(
787                     ccx.body.span,
788                     "Explicit promotion requested, but failed to promote",
789                 );
790             }
791
792             match candidate {
793                 Candidate::Argument { bb, index } if !is_promotable => {
794                     let span = ccx.body[bb].terminator().source_info.span;
795                     let msg = format!("argument {} is required to be a constant", index + 1);
796                     ccx.tcx.sess.span_err(span, &msg);
797                 }
798                 _ => (),
799             }
800
801             is_promotable
802         })
803         .collect()
804 }
805
806 struct Promoter<'a, 'tcx> {
807     tcx: TyCtxt<'tcx>,
808     source: &'a mut Body<'tcx>,
809     promoted: Body<'tcx>,
810     temps: &'a mut IndexVec<Local, TempState>,
811     extra_statements: &'a mut Vec<(Location, Statement<'tcx>)>,
812
813     /// If true, all nested temps are also kept in the
814     /// source MIR, not moved to the promoted MIR.
815     keep_original: bool,
816 }
817
818 impl<'a, 'tcx> Promoter<'a, 'tcx> {
819     fn new_block(&mut self) -> BasicBlock {
820         let span = self.promoted.span;
821         self.promoted.basic_blocks_mut().push(BasicBlockData {
822             statements: vec![],
823             terminator: Some(Terminator {
824                 source_info: SourceInfo::outermost(span),
825                 kind: TerminatorKind::Return,
826             }),
827             is_cleanup: false,
828         })
829     }
830
831     fn assign(&mut self, dest: Local, rvalue: Rvalue<'tcx>, span: Span) {
832         let last = self.promoted.basic_blocks().last().unwrap();
833         let data = &mut self.promoted[last];
834         data.statements.push(Statement {
835             source_info: SourceInfo::outermost(span),
836             kind: StatementKind::Assign(box (Place::from(dest), rvalue)),
837         });
838     }
839
840     fn is_temp_kind(&self, local: Local) -> bool {
841         self.source.local_kind(local) == LocalKind::Temp
842     }
843
844     /// Copies the initialization of this temp to the
845     /// promoted MIR, recursing through temps.
846     fn promote_temp(&mut self, temp: Local) -> Local {
847         let old_keep_original = self.keep_original;
848         let loc = match self.temps[temp] {
849             TempState::Defined { location, uses } if uses > 0 => {
850                 if uses > 1 {
851                     self.keep_original = true;
852                 }
853                 location
854             }
855             state => {
856                 span_bug!(self.promoted.span, "{:?} not promotable: {:?}", temp, state);
857             }
858         };
859         if !self.keep_original {
860             self.temps[temp] = TempState::PromotedOut;
861         }
862
863         let num_stmts = self.source[loc.block].statements.len();
864         let new_temp = self.promoted.local_decls.push(LocalDecl::new(
865             self.source.local_decls[temp].ty,
866             self.source.local_decls[temp].source_info.span,
867         ));
868
869         debug!("promote({:?} @ {:?}/{:?}, {:?})", temp, loc, num_stmts, self.keep_original);
870
871         // First, take the Rvalue or Call out of the source MIR,
872         // or duplicate it, depending on keep_original.
873         if loc.statement_index < num_stmts {
874             let (mut rvalue, source_info) = {
875                 let statement = &mut self.source[loc.block].statements[loc.statement_index];
876                 let rhs = match statement.kind {
877                     StatementKind::Assign(box (_, ref mut rhs)) => rhs,
878                     _ => {
879                         span_bug!(
880                             statement.source_info.span,
881                             "{:?} is not an assignment",
882                             statement
883                         );
884                     }
885                 };
886
887                 (
888                     if self.keep_original {
889                         rhs.clone()
890                     } else {
891                         let unit = Rvalue::Use(Operand::Constant(box Constant {
892                             span: statement.source_info.span,
893                             user_ty: None,
894                             literal: ty::Const::zero_sized(self.tcx, self.tcx.types.unit).into(),
895                         }));
896                         mem::replace(rhs, unit)
897                     },
898                     statement.source_info,
899                 )
900             };
901
902             self.visit_rvalue(&mut rvalue, loc);
903             self.assign(new_temp, rvalue, source_info.span);
904         } else {
905             let terminator = if self.keep_original {
906                 self.source[loc.block].terminator().clone()
907             } else {
908                 let terminator = self.source[loc.block].terminator_mut();
909                 let target = match terminator.kind {
910                     TerminatorKind::Call { destination: Some((_, target)), .. } => target,
911                     ref kind => {
912                         span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
913                     }
914                 };
915                 Terminator {
916                     source_info: terminator.source_info,
917                     kind: mem::replace(&mut terminator.kind, TerminatorKind::Goto { target }),
918                 }
919             };
920
921             match terminator.kind {
922                 TerminatorKind::Call { mut func, mut args, from_hir_call, fn_span, .. } => {
923                     self.visit_operand(&mut func, loc);
924                     for arg in &mut args {
925                         self.visit_operand(arg, loc);
926                     }
927
928                     let last = self.promoted.basic_blocks().last().unwrap();
929                     let new_target = self.new_block();
930
931                     *self.promoted[last].terminator_mut() = Terminator {
932                         kind: TerminatorKind::Call {
933                             func,
934                             args,
935                             cleanup: None,
936                             destination: Some((Place::from(new_temp), new_target)),
937                             from_hir_call,
938                             fn_span,
939                         },
940                         source_info: SourceInfo::outermost(terminator.source_info.span),
941                         ..terminator
942                     };
943                 }
944                 ref kind => {
945                     span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
946                 }
947             };
948         };
949
950         self.keep_original = old_keep_original;
951         new_temp
952     }
953
954     fn promote_candidate(
955         mut self,
956         candidate: Candidate,
957         next_promoted_id: usize,
958     ) -> Option<Body<'tcx>> {
959         let def = self.source.source.with_opt_param();
960         let mut rvalue = {
961             let promoted = &mut self.promoted;
962             let promoted_id = Promoted::new(next_promoted_id);
963             let tcx = self.tcx;
964             let mut promoted_operand = |ty, span| {
965                 promoted.span = span;
966                 promoted.local_decls[RETURN_PLACE] = LocalDecl::new(ty, span);
967
968                 Operand::Constant(Box::new(Constant {
969                     span,
970                     user_ty: None,
971                     literal: tcx
972                         .mk_const(ty::Const {
973                             ty,
974                             val: ty::ConstKind::Unevaluated(ty::Unevaluated {
975                                 def,
976                                 substs: InternalSubsts::for_item(tcx, def.did, |param, _| {
977                                     if let ty::GenericParamDefKind::Lifetime = param.kind {
978                                         tcx.lifetimes.re_erased.into()
979                                     } else {
980                                         tcx.mk_param_from_def(param)
981                                     }
982                                 }),
983                                 promoted: Some(promoted_id),
984                             }),
985                         })
986                         .into(),
987                 }))
988             };
989             let (blocks, local_decls) = self.source.basic_blocks_and_local_decls_mut();
990             match candidate {
991                 Candidate::Ref(loc) => {
992                     let statement = &mut blocks[loc.block].statements[loc.statement_index];
993                     match statement.kind {
994                         StatementKind::Assign(box (
995                             _,
996                             Rvalue::Ref(ref mut region, borrow_kind, ref mut place),
997                         )) => {
998                             // Use the underlying local for this (necessarily interior) borrow.
999                             let ty = local_decls.local_decls()[place.local].ty;
1000                             let span = statement.source_info.span;
1001
1002                             let ref_ty = tcx.mk_ref(
1003                                 tcx.lifetimes.re_erased,
1004                                 ty::TypeAndMut { ty, mutbl: borrow_kind.to_mutbl_lossy() },
1005                             );
1006
1007                             *region = tcx.lifetimes.re_erased;
1008
1009                             let mut projection = vec![PlaceElem::Deref];
1010                             projection.extend(place.projection);
1011                             place.projection = tcx.intern_place_elems(&projection);
1012
1013                             // Create a temp to hold the promoted reference.
1014                             // This is because `*r` requires `r` to be a local,
1015                             // otherwise we would use the `promoted` directly.
1016                             let mut promoted_ref = LocalDecl::new(ref_ty, span);
1017                             promoted_ref.source_info = statement.source_info;
1018                             let promoted_ref = local_decls.push(promoted_ref);
1019                             assert_eq!(self.temps.push(TempState::Unpromotable), promoted_ref);
1020
1021                             let promoted_ref_statement = Statement {
1022                                 source_info: statement.source_info,
1023                                 kind: StatementKind::Assign(Box::new((
1024                                     Place::from(promoted_ref),
1025                                     Rvalue::Use(promoted_operand(ref_ty, span)),
1026                                 ))),
1027                             };
1028                             self.extra_statements.push((loc, promoted_ref_statement));
1029
1030                             Rvalue::Ref(
1031                                 tcx.lifetimes.re_erased,
1032                                 borrow_kind,
1033                                 Place {
1034                                     local: mem::replace(&mut place.local, promoted_ref),
1035                                     projection: List::empty(),
1036                                 },
1037                             )
1038                         }
1039                         _ => bug!(),
1040                     }
1041                 }
1042                 Candidate::Argument { bb, index } => {
1043                     let terminator = blocks[bb].terminator_mut();
1044                     match terminator.kind {
1045                         TerminatorKind::Call { ref mut args, .. } => {
1046                             let ty = args[index].ty(local_decls, self.tcx);
1047                             let span = terminator.source_info.span;
1048
1049                             Rvalue::Use(mem::replace(&mut args[index], promoted_operand(ty, span)))
1050                         }
1051                         // We expected a `TerminatorKind::Call` for which we'd like to promote an
1052                         // argument. `qualify_consts` saw a `TerminatorKind::Call` here, but
1053                         // we are seeing a `Goto`. That means that the `promote_temps` method
1054                         // already promoted this call away entirely. This case occurs when calling
1055                         // a function requiring a constant argument and as that constant value
1056                         // providing a value whose computation contains another call to a function
1057                         // requiring a constant argument.
1058                         TerminatorKind::Goto { .. } => return None,
1059                         _ => bug!(),
1060                     }
1061                 }
1062             }
1063         };
1064
1065         assert_eq!(self.new_block(), START_BLOCK);
1066         self.visit_rvalue(
1067             &mut rvalue,
1068             Location { block: BasicBlock::new(0), statement_index: usize::MAX },
1069         );
1070
1071         let span = self.promoted.span;
1072         self.assign(RETURN_PLACE, rvalue, span);
1073         Some(self.promoted)
1074     }
1075 }
1076
1077 /// Replaces all temporaries with their promoted counterparts.
1078 impl<'a, 'tcx> MutVisitor<'tcx> for Promoter<'a, 'tcx> {
1079     fn tcx(&self) -> TyCtxt<'tcx> {
1080         self.tcx
1081     }
1082
1083     fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _: Location) {
1084         if self.is_temp_kind(*local) {
1085             *local = self.promote_temp(*local);
1086         }
1087     }
1088 }
1089
1090 pub fn promote_candidates<'tcx>(
1091     body: &mut Body<'tcx>,
1092     tcx: TyCtxt<'tcx>,
1093     mut temps: IndexVec<Local, TempState>,
1094     candidates: Vec<Candidate>,
1095 ) -> IndexVec<Promoted, Body<'tcx>> {
1096     // Visit candidates in reverse, in case they're nested.
1097     debug!("promote_candidates({:?})", candidates);
1098
1099     let mut promotions = IndexVec::new();
1100
1101     let mut extra_statements = vec![];
1102     for candidate in candidates.into_iter().rev() {
1103         match candidate {
1104             Candidate::Ref(Location { block, statement_index }) => {
1105                 if let StatementKind::Assign(box (place, _)) =
1106                     &body[block].statements[statement_index].kind
1107                 {
1108                     if let Some(local) = place.as_local() {
1109                         if temps[local] == TempState::PromotedOut {
1110                             // Already promoted.
1111                             continue;
1112                         }
1113                     }
1114                 }
1115             }
1116             Candidate::Argument { .. } => {}
1117         }
1118
1119         // Declare return place local so that `mir::Body::new` doesn't complain.
1120         let initial_locals = iter::once(LocalDecl::new(tcx.types.never, body.span)).collect();
1121
1122         let mut scope = body.source_scopes[candidate.source_info(body).scope].clone();
1123         scope.parent_scope = None;
1124
1125         let promoted = Body::new(
1126             body.source, // `promoted` gets filled in below
1127             IndexVec::new(),
1128             IndexVec::from_elem_n(scope, 1),
1129             initial_locals,
1130             IndexVec::new(),
1131             0,
1132             vec![],
1133             body.span,
1134             body.generator_kind(),
1135         );
1136
1137         let promoter = Promoter {
1138             promoted,
1139             tcx,
1140             source: body,
1141             temps: &mut temps,
1142             extra_statements: &mut extra_statements,
1143             keep_original: false,
1144         };
1145
1146         //FIXME(oli-obk): having a `maybe_push()` method on `IndexVec` might be nice
1147         if let Some(mut promoted) = promoter.promote_candidate(candidate, promotions.len()) {
1148             promoted.source.promoted = Some(promotions.next_index());
1149             promotions.push(promoted);
1150         }
1151     }
1152
1153     // Insert each of `extra_statements` before its indicated location, which
1154     // has to be done in reverse location order, to not invalidate the rest.
1155     extra_statements.sort_by_key(|&(loc, _)| cmp::Reverse(loc));
1156     for (loc, statement) in extra_statements {
1157         body[loc.block].statements.insert(loc.statement_index, statement);
1158     }
1159
1160     // Eliminate assignments to, and drops of promoted temps.
1161     let promoted = |index: Local| temps[index] == TempState::PromotedOut;
1162     for block in body.basic_blocks_mut() {
1163         block.statements.retain(|statement| match &statement.kind {
1164             StatementKind::Assign(box (place, _)) => {
1165                 if let Some(index) = place.as_local() {
1166                     !promoted(index)
1167                 } else {
1168                     true
1169                 }
1170             }
1171             StatementKind::StorageLive(index) | StatementKind::StorageDead(index) => {
1172                 !promoted(*index)
1173             }
1174             _ => true,
1175         });
1176         let terminator = block.terminator_mut();
1177         if let TerminatorKind::Drop { place, target, .. } = &terminator.kind {
1178             if let Some(index) = place.as_local() {
1179                 if promoted(index) {
1180                     terminator.kind = TerminatorKind::Goto { target: *target };
1181                 }
1182             }
1183         }
1184     }
1185
1186     promotions
1187 }
1188
1189 /// This function returns `true` if the function being called in the array
1190 /// repeat expression is a `const` function.
1191 crate fn is_const_fn_in_array_repeat_expression<'tcx>(
1192     ccx: &ConstCx<'_, 'tcx>,
1193     place: &Place<'tcx>,
1194     body: &Body<'tcx>,
1195 ) -> bool {
1196     match place.as_local() {
1197         // rule out cases such as: `let my_var = some_fn(); [my_var; N]`
1198         Some(local) if body.local_decls[local].is_user_variable() => return false,
1199         None => return false,
1200         _ => {}
1201     }
1202
1203     for block in body.basic_blocks() {
1204         if let Some(Terminator { kind: TerminatorKind::Call { func, destination, .. }, .. }) =
1205             &block.terminator
1206         {
1207             if let Operand::Constant(box Constant { literal, .. }) = func {
1208                 if let ty::FnDef(def_id, _) = *literal.ty().kind() {
1209                     if let Some((destination_place, _)) = destination {
1210                         if destination_place == place {
1211                             if is_const_fn(ccx.tcx, def_id) {
1212                                 return true;
1213                             }
1214                         }
1215                     }
1216                 }
1217             }
1218         }
1219     }
1220
1221     false
1222 }