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