]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_const_eval/src/transform/promote_consts.rs
New upstream version 1.74.1+dfsg1
[rustc.git] / compiler / rustc_const_eval / src / transform / promote_consts.rs
CommitLineData
a7813a04
XL
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
f9f354fc 15use rustc_hir as hir;
2b03887a 16use rustc_middle::mir;
ba9703b0
XL
17use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
18use rustc_middle::mir::*;
add651ee 19use rustc_middle::ty::GenericArgs;
fe692bf9 20use rustc_middle::ty::{self, List, Ty, TyCtxt, TypeVisitableExt};
fc512014 21use rustc_span::Span;
a7813a04 22
49aad941 23use rustc_index::{Idx, IndexSlice, IndexVec};
a7813a04 24
60c5eb7d 25use std::cell::Cell;
ba9703b0 26use std::{cmp, iter, mem};
a7813a04 27
3c0e092e 28use crate::transform::check_consts::{qualifs, ConstCx};
e74abb32 29
60c5eb7d
XL
30/// A `MirPass` for promotion.
31///
17df50a5
XL
32/// Promotion is the extraction of promotable temps into separate MIR bodies so they can have
33/// `'static` lifetime.
60c5eb7d
XL
34///
35/// After this pass is run, `promoted_fragments` will hold the MIR body corresponding to each
dfeec247 36/// newly created `Constant`.
60c5eb7d
XL
37#[derive(Default)]
38pub struct PromoteTemps<'tcx> {
f9f354fc 39 pub promoted_fragments: Cell<IndexVec<Promoted, Body<'tcx>>>,
60c5eb7d
XL
40}
41
42impl<'tcx> MirPass<'tcx> for PromoteTemps<'tcx> {
29967ef6 43 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
60c5eb7d
XL
44 // There's not really any point in promoting errorful MIR.
45 //
46 // This does not include MIR that failed const-checking, which we still try to promote.
487cf647
FG
47 if let Err(_) = body.return_ty().error_reported() {
48 debug!("PromoteTemps: MIR had errors");
60c5eb7d
XL
49 return;
50 }
29967ef6 51 if body.source.promoted.is_some() {
60c5eb7d
XL
52 return;
53 }
54
29967ef6 55 let ccx = ConstCx::new(tcx, body);
fe692bf9 56 let (mut temps, all_candidates) = collect_temps_and_candidates(&ccx);
60c5eb7d 57
04454e1e 58 let promotable_candidates = validate_candidates(&ccx, &mut temps, &all_candidates);
60c5eb7d 59
29967ef6 60 let promoted = promote_candidates(body, tcx, temps, promotable_candidates);
60c5eb7d
XL
61 self.promoted_fragments.set(promoted);
62 }
63}
64
a7813a04
XL
65/// State of a temporary during collection and promotion.
66#[derive(Copy, Clone, PartialEq, Eq, Debug)]
67pub enum TempState {
68 /// No references to this temp.
69 Undefined,
70 /// One direct assignment and any number of direct uses.
71 /// A borrow of this temp is promotable if the assigned
72 /// value is qualified as constant.
04454e1e 73 Defined { location: Location, uses: usize, valid: Result<(), ()> },
a7813a04
XL
74 /// Any other combination of assignments/uses.
75 Unpromotable,
76 /// This temp was part of an rvalue which got extracted
77 /// during promotion and needs cleanup.
dfeec247 78 PromotedOut,
a7813a04
XL
79}
80
81impl TempState {
82 pub fn is_promotable(&self) -> bool {
13cf67c4 83 debug!("is_promotable: self={:?}", self);
5869c6ff 84 matches!(self, TempState::Defined { .. })
a7813a04
XL
85 }
86}
87
88/// A "root candidate" for promotion, which will become the
89/// returned value in a promoted MIR, unless it's a subset
90/// of a larger candidate.
e74abb32 91#[derive(Copy, Clone, PartialEq, Eq, Debug)]
3c0e092e
XL
92pub struct Candidate {
93 location: Location,
e74abb32
XL
94}
95
e74abb32 96struct Collector<'a, 'tcx> {
f9f354fc 97 ccx: &'a ConstCx<'a, 'tcx>,
c30ab7b3 98 temps: IndexVec<Local, TempState>,
e74abb32 99 candidates: Vec<Candidate>,
a7813a04
XL
100}
101
e74abb32 102impl<'tcx> Visitor<'tcx> for Collector<'_, 'tcx> {
064997fb 103 fn visit_local(&mut self, index: Local, context: PlaceContext, location: Location) {
13cf67c4 104 debug!("visit_local: index={:?} context={:?} location={:?}", index, context, location);
48663c56 105 // We're only interested in temporaries and the return place
f9f354fc 106 match self.ccx.body.local_kind(index) {
353b0b11
FG
107 LocalKind::Arg => return,
108 LocalKind::Temp if self.ccx.body.local_decls[index].is_user_variable() => return,
109 LocalKind::ReturnPointer | LocalKind::Temp => {}
ea8adc8c 110 }
c30ab7b3 111
ea8adc8c
XL
112 // Ignore drops, if the temp gets promoted,
113 // then it's constant and thus drop is noop.
60c5eb7d 114 // Non-uses are also irrelevant.
13cf67c4
XL
115 if context.is_drop() || !context.is_use() {
116 debug!(
117 "visit_local: context.is_drop={:?} context.is_use={:?}",
dfeec247
XL
118 context.is_drop(),
119 context.is_use(),
13cf67c4 120 );
ea8adc8c
XL
121 return;
122 }
a7813a04 123
ea8adc8c 124 let temp = &mut self.temps[index];
13cf67c4 125 debug!("visit_local: temp={:?}", temp);
ea8adc8c
XL
126 if *temp == TempState::Undefined {
127 match context {
dfeec247
XL
128 PlaceContext::MutatingUse(MutatingUseContext::Store)
129 | PlaceContext::MutatingUse(MutatingUseContext::Call) => {
04454e1e 130 *temp = TempState::Defined { location, uses: 0, valid: Err(()) };
32a655c1 131 return;
a7813a04 132 }
ea8adc8c 133 _ => { /* mark as unpromotable below */ }
a7813a04 134 }
9c376795 135 } else if let TempState::Defined { uses, .. } = temp {
ea8adc8c 136 // We always allow borrows, even mutable ones, as we need
0731742a 137 // to promote mutable borrows of some ZSTs e.g., `&mut []`.
dfeec247
XL
138 let allowed_use = match context {
139 PlaceContext::MutatingUse(MutatingUseContext::Borrow)
140 | PlaceContext::NonMutatingUse(_) => true,
141 PlaceContext::MutatingUse(_) | PlaceContext::NonUse(_) => false,
142 };
13cf67c4 143 debug!("visit_local: allowed_use={:?}", allowed_use);
ea8adc8c
XL
144 if allowed_use {
145 *uses += 1;
146 return;
147 }
148 /* mark as unpromotable below */
a7813a04 149 }
ea8adc8c 150 *temp = TempState::Unpromotable;
a7813a04
XL
151 }
152
e74abb32
XL
153 fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
154 self.super_rvalue(rvalue, location);
155
156 match *rvalue {
157 Rvalue::Ref(..) => {
3c0e092e 158 self.candidates.push(Candidate { location });
e74abb32 159 }
e74abb32
XL
160 _ => {}
161 }
162 }
a7813a04
XL
163}
164
a2a8927a
XL
165pub fn collect_temps_and_candidates<'tcx>(
166 ccx: &ConstCx<'_, 'tcx>,
e74abb32
XL
167) -> (IndexVec<Local, TempState>, Vec<Candidate>) {
168 let mut collector = Collector {
f9f354fc 169 temps: IndexVec::from_elem(TempState::Undefined, &ccx.body.local_decls),
e74abb32 170 candidates: vec![],
f9f354fc 171 ccx,
a7813a04 172 };
fe692bf9 173 for (bb, data) in traversal::reverse_postorder(ccx.body) {
a7813a04
XL
174 collector.visit_basic_block_data(bb, data);
175 }
e74abb32
XL
176 (collector.temps, collector.candidates)
177}
178
179/// Checks whether locals that appear in a promotion context (`Candidate`) are actually promotable.
180///
181/// This wraps an `Item`, and has access to all fields of that `Item` via `Deref` coercion.
182struct Validator<'a, 'tcx> {
f9f354fc 183 ccx: &'a ConstCx<'a, 'tcx>,
353b0b11 184 temps: &'a mut IndexSlice<Local, TempState>,
e74abb32
XL
185}
186
a2a8927a 187impl<'a, 'tcx> std::ops::Deref for Validator<'a, 'tcx> {
f9f354fc 188 type Target = ConstCx<'a, 'tcx>;
e74abb32
XL
189
190 fn deref(&self) -> &Self::Target {
f9f354fc 191 &self.ccx
e74abb32
XL
192 }
193}
194
195struct Unpromotable;
196
197impl<'tcx> Validator<'_, 'tcx> {
04454e1e 198 fn validate_candidate(&mut self, candidate: Candidate) -> Result<(), Unpromotable> {
3c0e092e
XL
199 let loc = candidate.location;
200 let statement = &self.body[loc.block].statements[loc.statement_index];
201 match &statement.kind {
202 StatementKind::Assign(box (_, Rvalue::Ref(_, kind, place))) => {
203 // We can only promote interior borrows of promotable temps (non-temps
204 // don't get promoted anyway).
205 self.validate_local(place.local)?;
206
207 // The reference operation itself must be promotable.
208 // (Needs to come after `validate_local` to avoid ICEs.)
209 self.validate_ref(*kind, place)?;
e74abb32 210
3c0e092e
XL
211 // We do not check all the projections (they do not get promoted anyway),
212 // but we do stay away from promoting anything involving a dereference.
213 if place.projection.contains(&ProjectionElem::Deref) {
214 return Err(Unpromotable);
215 }
e74abb32 216
3c0e092e 217 Ok(())
e74abb32 218 }
3c0e092e 219 _ => bug!(),
e74abb32
XL
220 }
221 }
222
223 // FIXME(eddyb) maybe cache this?
04454e1e 224 fn qualif_local<Q: qualifs::Qualif>(&mut self, local: Local) -> bool {
e74abb32
XL
225 if let TempState::Defined { location: loc, .. } = self.temps[local] {
226 let num_stmts = self.body[loc.block].statements.len();
227
228 if loc.statement_index < num_stmts {
229 let statement = &self.body[loc.block].statements[loc.statement_index];
230 match &statement.kind {
ba9703b0 231 StatementKind::Assign(box (_, rhs)) => qualifs::in_rvalue::<Q, _>(
f9f354fc 232 &self.ccx,
ba9703b0
XL
233 &mut |l| self.qualif_local::<Q>(l),
234 rhs,
235 ),
e74abb32 236 _ => {
dfeec247
XL
237 span_bug!(
238 statement.source_info.span,
239 "{:?} is not an assignment",
240 statement
241 );
e74abb32
XL
242 }
243 }
244 } else {
245 let terminator = self.body[loc.block].terminator();
246 match &terminator.kind {
ba9703b0 247 TerminatorKind::Call { .. } => {
e74abb32 248 let return_ty = self.body.local_decls[local].ty;
f9f354fc 249 Q::in_any_value_of_ty(&self.ccx, return_ty)
e74abb32
XL
250 }
251 kind => {
252 span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
253 }
254 }
255 }
256 } else {
9c376795 257 false
e74abb32
XL
258 }
259 }
260
04454e1e
FG
261 fn validate_local(&mut self, local: Local) -> Result<(), Unpromotable> {
262 if let TempState::Defined { location: loc, uses, valid } = self.temps[local] {
9c376795
FG
263 // We cannot promote things that need dropping, since the promoted value
264 // would not get dropped.
265 if self.qualif_local::<qualifs::NeedsDrop>(local) {
266 return Err(Unpromotable);
267 }
04454e1e
FG
268 valid.or_else(|_| {
269 let ok = {
270 let block = &self.body[loc.block];
271 let num_stmts = block.statements.len();
272
273 if loc.statement_index < num_stmts {
274 let statement = &block.statements[loc.statement_index];
275 match &statement.kind {
276 StatementKind::Assign(box (_, rhs)) => self.validate_rvalue(rhs),
277 _ => {
278 span_bug!(
279 statement.source_info.span,
280 "{:?} is not an assignment",
281 statement
282 );
283 }
284 }
285 } else {
286 let terminator = block.terminator();
287 match &terminator.kind {
288 TerminatorKind::Call { func, args, .. } => {
289 self.validate_call(func, args)
290 }
291 TerminatorKind::Yield { .. } => Err(Unpromotable),
292 kind => {
293 span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
294 }
295 }
e74abb32 296 }
04454e1e
FG
297 };
298 self.temps[local] = match ok {
299 Ok(()) => TempState::Defined { location: loc, uses, valid: Ok(()) },
300 Err(_) => TempState::Unpromotable,
301 };
302 ok
303 })
e74abb32
XL
304 } else {
305 Err(Unpromotable)
306 }
307 }
308
04454e1e 309 fn validate_place(&mut self, place: PlaceRef<'tcx>) -> Result<(), Unpromotable> {
5869c6ff
XL
310 match place.last_projection() {
311 None => self.validate_local(place.local),
312 Some((place_base, elem)) => {
1b1a35ee 313 // Validate topmost projection, then recurse.
5869c6ff 314 match elem {
3dfed10e 315 ProjectionElem::Deref => {
1b1a35ee 316 let mut promotable = false;
487cf647
FG
317 // When a static is used by-value, that gets desugared to `*STATIC_ADDR`,
318 // and we need to be able to promote this. So check if this deref matches
319 // that specific pattern.
320
5869c6ff
XL
321 // We need to make sure this is a `Deref` of a local with no further projections.
322 // Discussion can be found at
323 // https://github.com/rust-lang/rust/pull/74945#discussion_r463063247
324 if let Some(local) = place_base.as_local() {
5869c6ff
XL
325 if let TempState::Defined { location, .. } = self.temps[local] {
326 let def_stmt = self.body[location.block]
327 .statements
328 .get(location.statement_index);
329 if let Some(Statement {
330 kind:
331 StatementKind::Assign(box (
332 _,
333 Rvalue::Use(Operand::Constant(c)),
334 )),
335 ..
336 }) = def_stmt
337 {
338 if let Some(did) = c.check_static_ptr(self.tcx) {
339 // Evaluating a promoted may not read statics except if it got
340 // promoted from a static (this is a CTFE check). So we
341 // can only promote static accesses inside statics.
342 if let Some(hir::ConstContext::Static(..)) = self.const_kind
3dfed10e 343 {
5869c6ff
XL
344 if !self.tcx.is_thread_local_static(did) {
345 promotable = true;
346 }
3dfed10e
XL
347 }
348 }
349 }
350 }
351 }
1b1a35ee 352 if !promotable {
3dfed10e
XL
353 return Err(Unpromotable);
354 }
355 }
2b03887a 356 ProjectionElem::OpaqueCast(..) | ProjectionElem::Downcast(..) => {
dfeec247
XL
357 return Err(Unpromotable);
358 }
e74abb32 359
781aab86
FG
360 ProjectionElem::ConstantIndex { .. }
361 | ProjectionElem::Subtype(_)
362 | ProjectionElem::Subslice { .. } => {}
e74abb32
XL
363
364 ProjectionElem::Index(local) => {
17df50a5
XL
365 let mut promotable = false;
366 // Only accept if we can predict the index and are indexing an array.
9ffffee4
FG
367 let val = if let TempState::Defined { location: loc, .. } =
368 self.temps[local]
369 {
370 let block = &self.body[loc.block];
371 if loc.statement_index < block.statements.len() {
372 let statement = &block.statements[loc.statement_index];
373 match &statement.kind {
374 StatementKind::Assign(box (
375 _,
376 Rvalue::Use(Operand::Constant(c)),
781aab86 377 )) => c.const_.try_eval_target_usize(self.tcx, self.param_env),
9ffffee4 378 _ => None,
5869c6ff
XL
379 }
380 } else {
381 None
9ffffee4
FG
382 }
383 } else {
384 None
385 };
17df50a5
XL
386 if let Some(idx) = val {
387 // Determine the type of the thing we are indexing.
388 let ty = place_base.ty(self.body, self.tcx).ty;
389 match ty.kind() {
390 ty::Array(_, len) => {
391 // It's an array; determine its length.
9ffffee4
FG
392 if let Some(len) =
393 len.try_eval_target_usize(self.tcx, self.param_env)
17df50a5
XL
394 {
395 // If the index is in-bounds, go ahead.
396 if idx < len {
397 promotable = true;
5869c6ff
XL
398 }
399 }
5869c6ff 400 }
17df50a5 401 _ => {}
5869c6ff
XL
402 }
403 }
17df50a5
XL
404 if !promotable {
405 return Err(Unpromotable);
406 }
407
e74abb32
XL
408 self.validate_local(local)?;
409 }
410
411 ProjectionElem::Field(..) => {
5869c6ff 412 let base_ty = place_base.ty(self.body, self.tcx).ty;
17df50a5 413 if base_ty.is_union() {
29967ef6 414 // No promotion of union field accesses.
17df50a5 415 return Err(Unpromotable);
e74abb32
XL
416 }
417 }
418 }
419
5869c6ff 420 self.validate_place(place_base)
e74abb32
XL
421 }
422 }
423 }
424
04454e1e 425 fn validate_operand(&mut self, operand: &Operand<'tcx>) -> Result<(), Unpromotable> {
e74abb32 426 match operand {
dfeec247 427 Operand::Copy(place) | Operand::Move(place) => self.validate_place(place.as_ref()),
e74abb32 428
60c5eb7d
XL
429 // The qualifs for a constant (e.g. `HasMutInterior`) are checked in
430 // `validate_rvalue` upon access.
431 Operand::Constant(c) => {
432 if let Some(def_id) = c.check_static_ptr(self.tcx) {
433 // Only allow statics (not consts) to refer to other statics.
434 // FIXME(eddyb) does this matter at all for promotion?
1b1a35ee
XL
435 // FIXME(RalfJung) it makes little sense to not promote this in `fn`/`const fn`,
436 // and in `const` this cannot occur anyway. The only concern is that we might
437 // promote even `let x = &STATIC` which would be useless, but this applies to
438 // promotion inside statics as well.
f9f354fc 439 let is_static = matches!(self.const_kind, Some(hir::ConstContext::Static(_)));
60c5eb7d
XL
440 if !is_static {
441 return Err(Unpromotable);
442 }
443
f9f354fc 444 let is_thread_local = self.tcx.is_thread_local_static(def_id);
60c5eb7d
XL
445 if is_thread_local {
446 return Err(Unpromotable);
e74abb32
XL
447 }
448 }
449
450 Ok(())
dfeec247 451 }
e74abb32
XL
452 }
453 }
454
04454e1e 455 fn validate_ref(&mut self, kind: BorrowKind, place: &Place<'tcx>) -> Result<(), Unpromotable> {
5869c6ff
XL
456 match kind {
457 // Reject these borrow types just to be safe.
458 // FIXME(RalfJung): could we allow them? Should we? No point in it until we have a usecase.
781aab86 459 BorrowKind::Fake | BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture } => {
fe692bf9
FG
460 return Err(Unpromotable);
461 }
5869c6ff
XL
462
463 BorrowKind::Shared => {
464 let has_mut_interior = self.qualif_local::<qualifs::HasMutInterior>(place.local);
465 if has_mut_interior {
1b1a35ee 466 return Err(Unpromotable);
e74abb32
XL
467 }
468 }
469
fe692bf9
FG
470 // FIXME: consider changing this to only promote &mut [] for default borrows,
471 // also forbidding two phase borrows
472 BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::TwoPhaseBorrow } => {
5869c6ff
XL
473 let ty = place.ty(self.body, self.tcx).ty;
474
475 // In theory, any zero-sized value could be borrowed
476 // mutably without consequences. However, only &mut []
477 // is allowed right now.
478 if let ty::Array(_, len) = ty.kind() {
9ffffee4 479 match len.try_eval_target_usize(self.tcx, self.param_env) {
5869c6ff
XL
480 Some(0) => {}
481 _ => return Err(Unpromotable),
482 }
483 } else {
e74abb32
XL
484 return Err(Unpromotable);
485 }
486 }
e74abb32
XL
487 }
488
5869c6ff
XL
489 Ok(())
490 }
491
04454e1e 492 fn validate_rvalue(&mut self, rvalue: &Rvalue<'tcx>) -> Result<(), Unpromotable> {
e74abb32 493 match rvalue {
5869c6ff
XL
494 Rvalue::Use(operand) | Rvalue::Repeat(operand, _) => {
495 self.validate_operand(operand)?;
496 }
064997fb
FG
497 Rvalue::CopyForDeref(place) => {
498 let op = &Operand::Copy(*place);
499 self.validate_operand(op)?
500 }
5869c6ff
XL
501
502 Rvalue::Discriminant(place) | Rvalue::Len(place) => {
503 self.validate_place(place.as_ref())?
504 }
505
506 Rvalue::ThreadLocalRef(_) => return Err(Unpromotable),
507
923072b8
FG
508 // ptr-to-int casts are not possible in consts and thus not promotable
509 Rvalue::Cast(CastKind::PointerExposeAddress, _, _) => return Err(Unpromotable),
5869c6ff 510
923072b8
FG
511 // all other casts including int-to-ptr casts are fine, they just use the integer value
512 // at pointer type.
513 Rvalue::Cast(_, operand, _) => {
5869c6ff
XL
514 self.validate_operand(operand)?;
515 }
516
517 Rvalue::NullaryOp(op, _) => match op {
5869c6ff 518 NullOp::SizeOf => {}
c295e0f8 519 NullOp::AlignOf => {}
49aad941 520 NullOp::OffsetOf(_) => {}
5869c6ff 521 },
f9f354fc 522
c295e0f8
XL
523 Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable),
524
5869c6ff
XL
525 Rvalue::UnaryOp(op, operand) => {
526 match op {
527 // These operations can never fail.
528 UnOp::Neg | UnOp::Not => {}
529 }
e74abb32 530
5869c6ff
XL
531 self.validate_operand(operand)?;
532 }
533
6a06907d 534 Rvalue::BinaryOp(op, box (lhs, rhs)) | Rvalue::CheckedBinaryOp(op, box (lhs, rhs)) => {
5869c6ff
XL
535 let op = *op;
536 let lhs_ty = lhs.ty(self.body, self.tcx);
537
538 if let ty::RawPtr(_) | ty::FnPtr(..) = lhs_ty.kind() {
539 // Raw and fn pointer operations are not allowed inside consts and thus not promotable.
540 assert!(matches!(
541 op,
542 BinOp::Eq
543 | BinOp::Ne
544 | BinOp::Le
545 | BinOp::Lt
546 | BinOp::Ge
547 | BinOp::Gt
548 | BinOp::Offset
549 ));
550 return Err(Unpromotable);
551 }
e74abb32 552
5869c6ff
XL
553 match op {
554 BinOp::Div | BinOp::Rem => {
17df50a5 555 if lhs_ty.is_integral() {
5869c6ff
XL
556 // Integer division: the RHS must be a non-zero const.
557 let const_val = match rhs {
558 Operand::Constant(c) => {
781aab86 559 c.const_.try_eval_bits(self.tcx, self.param_env)
5869c6ff
XL
560 }
561 _ => None,
562 };
563 match const_val {
564 Some(x) if x != 0 => {} // okay
565 _ => return Err(Unpromotable), // value not known or 0 -- not okay
566 }
567 }
568 }
569 // The remaining operations can never fail.
570 BinOp::Eq
571 | BinOp::Ne
572 | BinOp::Le
573 | BinOp::Lt
574 | BinOp::Ge
575 | BinOp::Gt
576 | BinOp::Offset
577 | BinOp::Add
fe692bf9 578 | BinOp::AddUnchecked
5869c6ff 579 | BinOp::Sub
fe692bf9 580 | BinOp::SubUnchecked
5869c6ff 581 | BinOp::Mul
fe692bf9 582 | BinOp::MulUnchecked
5869c6ff
XL
583 | BinOp::BitXor
584 | BinOp::BitAnd
585 | BinOp::BitOr
586 | BinOp::Shl
fe692bf9
FG
587 | BinOp::ShlUnchecked
588 | BinOp::Shr
589 | BinOp::ShrUnchecked => {}
5869c6ff 590 }
e74abb32 591
e74abb32 592 self.validate_operand(lhs)?;
5869c6ff 593 self.validate_operand(rhs)?;
e74abb32
XL
594 }
595
dfeec247 596 Rvalue::AddressOf(_, place) => {
1b1a35ee
XL
597 // We accept `&raw *`, i.e., raw reborrows -- creating a raw pointer is
598 // no problem, only using it is.
5869c6ff
XL
599 if let Some((place_base, ProjectionElem::Deref)) = place.as_ref().last_projection()
600 {
601 let base_ty = place_base.ty(self.body, self.tcx).ty;
1b1a35ee 602 if let ty::Ref(..) = base_ty.kind() {
5869c6ff 603 return self.validate_place(place_base);
dfeec247
XL
604 }
605 }
5869c6ff 606 return Err(Unpromotable);
dfeec247
XL
607 }
608
e74abb32 609 Rvalue::Ref(_, kind, place) => {
e74abb32 610 // Special-case reborrows to be more like a copy of the reference.
5869c6ff
XL
611 let mut place_simplified = place.as_ref();
612 if let Some((place_base, ProjectionElem::Deref)) =
613 place_simplified.last_projection()
614 {
615 let base_ty = place_base.ty(self.body, self.tcx).ty;
1b1a35ee 616 if let ty::Ref(..) = base_ty.kind() {
5869c6ff 617 place_simplified = place_base;
e74abb32
XL
618 }
619 }
620
5869c6ff 621 self.validate_place(place_simplified)?;
e74abb32 622
5869c6ff
XL
623 // Check that the reference is fine (using the original place!).
624 // (Needs to come after `validate_place` to avoid ICEs.)
625 self.validate_ref(*kind, place)?;
e74abb32
XL
626 }
627
5869c6ff 628 Rvalue::Aggregate(_, operands) => {
e74abb32
XL
629 for o in operands {
630 self.validate_operand(o)?;
631 }
e74abb32
XL
632 }
633 }
5869c6ff
XL
634
635 Ok(())
e74abb32
XL
636 }
637
638 fn validate_call(
04454e1e 639 &mut self,
e74abb32
XL
640 callee: &Operand<'tcx>,
641 args: &[Operand<'tcx>],
642 ) -> Result<(), Unpromotable> {
f9f354fc 643 let fn_ty = callee.ty(self.body, self.tcx);
e74abb32 644
17df50a5 645 // Inside const/static items, we promote all (eligible) function calls.
29967ef6 646 // Everywhere else, we require `#[rustc_promotable]` on the callee.
17df50a5
XL
647 let promote_all_const_fn = matches!(
648 self.const_kind,
781aab86 649 Some(hir::ConstContext::Static(_) | hir::ConstContext::Const { inline: false })
17df50a5 650 );
29967ef6 651 if !promote_all_const_fn {
1b1a35ee 652 if let ty::FnDef(def_id, _) = *fn_ty.kind() {
e74abb32
XL
653 // Never promote runtime `const fn` calls of
654 // functions without `#[rustc_promotable]`.
655 if !self.tcx.is_promotable_const_fn(def_id) {
656 return Err(Unpromotable);
657 }
658 }
659 }
660
1b1a35ee 661 let is_const_fn = match *fn_ty.kind() {
3c0e092e 662 ty::FnDef(def_id, _) => self.tcx.is_const_fn_raw(def_id),
e74abb32
XL
663 _ => false,
664 };
665 if !is_const_fn {
666 return Err(Unpromotable);
667 }
668
669 self.validate_operand(callee)?;
670 for arg in args {
671 self.validate_operand(arg)?;
672 }
673
674 Ok(())
675 }
676}
677
678// FIXME(eddyb) remove the differences for promotability in `static`, `const`, `const fn`.
679pub fn validate_candidates(
f9f354fc 680 ccx: &ConstCx<'_, '_>,
353b0b11 681 temps: &mut IndexSlice<Local, TempState>,
e74abb32
XL
682 candidates: &[Candidate],
683) -> Vec<Candidate> {
04454e1e 684 let mut validator = Validator { ccx, temps };
e74abb32 685
dfeec247
XL
686 candidates
687 .iter()
688 .copied()
17df50a5 689 .filter(|&candidate| validator.validate_candidate(candidate).is_ok())
dfeec247 690 .collect()
a7813a04
XL
691}
692
dc9dc135
XL
693struct Promoter<'a, 'tcx> {
694 tcx: TyCtxt<'tcx>,
f9f354fc
XL
695 source: &'a mut Body<'tcx>,
696 promoted: Body<'tcx>,
c30ab7b3 697 temps: &'a mut IndexVec<Local, TempState>,
dfeec247 698 extra_statements: &'a mut Vec<(Location, Statement<'tcx>)>,
a7813a04
XL
699
700 /// If true, all nested temps are also kept in the
701 /// source MIR, not moved to the promoted MIR.
dc9dc135 702 keep_original: bool,
a7813a04
XL
703}
704
705impl<'a, 'tcx> Promoter<'a, 'tcx> {
706 fn new_block(&mut self) -> BasicBlock {
3157f602
XL
707 let span = self.promoted.span;
708 self.promoted.basic_blocks_mut().push(BasicBlockData {
a7813a04
XL
709 statements: vec![],
710 terminator: Some(Terminator {
f9f354fc 711 source_info: SourceInfo::outermost(span),
dfeec247 712 kind: TerminatorKind::Return,
a7813a04 713 }),
dfeec247 714 is_cleanup: false,
3157f602 715 })
a7813a04
XL
716 }
717
c30ab7b3 718 fn assign(&mut self, dest: Local, rvalue: Rvalue<'tcx>, span: Span) {
353b0b11 719 let last = self.promoted.basic_blocks.last_index().unwrap();
3157f602 720 let data = &mut self.promoted[last];
a7813a04 721 data.statements.push(Statement {
f9f354fc 722 source_info: SourceInfo::outermost(span),
94222f64 723 kind: StatementKind::Assign(Box::new((Place::from(dest), rvalue))),
a7813a04
XL
724 });
725 }
726
e74abb32
XL
727 fn is_temp_kind(&self, local: Local) -> bool {
728 self.source.local_kind(local) == LocalKind::Temp
729 }
730
9fa01778 731 /// Copies the initialization of this temp to the
a7813a04 732 /// promoted MIR, recursing through temps.
c30ab7b3 733 fn promote_temp(&mut self, temp: Local) -> Local {
a7813a04 734 let old_keep_original = self.keep_original;
476ff2be 735 let loc = match self.temps[temp] {
04454e1e 736 TempState::Defined { location, uses, .. } if uses > 0 => {
a7813a04
XL
737 if uses > 1 {
738 self.keep_original = true;
739 }
476ff2be 740 location
a7813a04 741 }
dfeec247
XL
742 state => {
743 span_bug!(self.promoted.span, "{:?} not promotable: {:?}", temp, state);
a7813a04
XL
744 }
745 };
746 if !self.keep_original {
3157f602 747 self.temps[temp] = TempState::PromotedOut;
a7813a04
XL
748 }
749
e74abb32 750 let num_stmts = self.source[loc.block].statements.len();
f9f354fc 751 let new_temp = self.promoted.local_decls.push(LocalDecl::new(
dfeec247
XL
752 self.source.local_decls[temp].ty,
753 self.source.local_decls[temp].source_info.span,
754 ));
476ff2be 755
dfeec247 756 debug!("promote({:?} @ {:?}/{:?}, {:?})", temp, loc, num_stmts, self.keep_original);
a7813a04
XL
757
758 // First, take the Rvalue or Call out of the source MIR,
759 // or duplicate it, depending on keep_original.
e74abb32 760 if loc.statement_index < num_stmts {
e1599b0c 761 let (mut rvalue, source_info) = {
476ff2be 762 let statement = &mut self.source[loc.block].statements[loc.statement_index];
9c376795 763 let StatementKind::Assign(box (_, rhs)) = &mut statement.kind else {
add651ee 764 span_bug!(statement.source_info.span, "{:?} is not an assignment", statement);
476ff2be
SL
765 };
766
dfeec247
XL
767 (
768 if self.keep_original {
769 rhs.clone()
770 } else {
781aab86 771 let unit = Rvalue::Use(Operand::Constant(Box::new(ConstOperand {
ba9703b0
XL
772 span: statement.source_info.span,
773 user_ty: None,
781aab86 774 const_: Const::zero_sized(self.tcx.types.unit),
94222f64 775 })));
dfeec247
XL
776 mem::replace(rhs, unit)
777 },
778 statement.source_info,
779 )
5bcae85e 780 };
476ff2be
SL
781
782 self.visit_rvalue(&mut rvalue, loc);
783 self.assign(new_temp, rvalue, source_info.span);
a7813a04 784 } else {
476ff2be
SL
785 let terminator = if self.keep_original {
786 self.source[loc.block].terminator().clone()
787 } else {
788 let terminator = self.source[loc.block].terminator_mut();
9c376795
FG
789 let target = match &terminator.kind {
790 TerminatorKind::Call { target: Some(target), .. } => *target,
791 kind => {
476ff2be
SL
792 span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
793 }
794 };
795 Terminator {
796 source_info: terminator.source_info,
dfeec247 797 kind: mem::replace(&mut terminator.kind, TerminatorKind::Goto { target }),
a7813a04
XL
798 }
799 };
a7813a04 800
476ff2be 801 match terminator.kind {
fe692bf9
FG
802 TerminatorKind::Call {
803 mut func, mut args, call_source: desugar, fn_span, ..
804 } => {
476ff2be
SL
805 self.visit_operand(&mut func, loc);
806 for arg in &mut args {
807 self.visit_operand(arg, loc);
808 }
a7813a04 809
353b0b11 810 let last = self.promoted.basic_blocks.last_index().unwrap();
476ff2be 811 let new_target = self.new_block();
a7813a04 812
476ff2be
SL
813 *self.promoted[last].terminator_mut() = Terminator {
814 kind: TerminatorKind::Call {
3b2f2976
XL
815 func,
816 args,
353b0b11 817 unwind: UnwindAction::Continue,
923072b8
FG
818 destination: Place::from(new_temp),
819 target: Some(new_target),
fe692bf9 820 call_source: desugar,
f035d41b 821 fn_span,
476ff2be 822 },
29967ef6 823 source_info: SourceInfo::outermost(terminator.source_info.span),
476ff2be
SL
824 ..terminator
825 };
a7813a04 826 }
9c376795 827 kind => {
476ff2be
SL
828 span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
829 }
830 };
831 };
a7813a04 832
a7813a04 833 self.keep_original = old_keep_original;
3157f602 834 new_temp
a7813a04
XL
835 }
836
3c0e092e 837 fn promote_candidate(mut self, candidate: Candidate, next_promoted_id: usize) -> Body<'tcx> {
49aad941 838 let def = self.source.source.def_id();
dfeec247 839 let mut rvalue = {
94b46f34 840 let promoted = &mut self.promoted;
e1599b0c
XL
841 let promoted_id = Promoted::new(next_promoted_id);
842 let tcx = self.tcx;
dfeec247 843 let mut promoted_operand = |ty, span| {
94b46f34 844 promoted.span = span;
f9f354fc 845 promoted.local_decls[RETURN_PLACE] = LocalDecl::new(ty, span);
add651ee
FG
846 let args = tcx.erase_regions(GenericArgs::identity_for_item(tcx, def));
847 let uneval = mir::UnevaluatedConst { def, args, promoted: Some(promoted_id) };
dfeec247 848
781aab86 849 Operand::Constant(Box::new(ConstOperand {
dfeec247
XL
850 span,
851 user_ty: None,
781aab86 852 const_: Const::Unevaluated(uneval, ty),
dfeec247 853 }))
94b46f34 854 };
f2b60f7d 855
064997fb
FG
856 let blocks = self.source.basic_blocks.as_mut();
857 let local_decls = &mut self.source.local_decls;
3c0e092e
XL
858 let loc = candidate.location;
859 let statement = &mut blocks[loc.block].statements[loc.statement_index];
add651ee
FG
860 let StatementKind::Assign(box (_, Rvalue::Ref(region, borrow_kind, place))) =
861 &mut statement.kind
862 else {
9c376795
FG
863 bug!()
864 };
3c0e092e 865
9c376795
FG
866 // Use the underlying local for this (necessarily interior) borrow.
867 let ty = local_decls[place.local].ty;
868 let span = statement.source_info.span;
869
fe692bf9
FG
870 let ref_ty = Ty::new_ref(
871 tcx,
9c376795
FG
872 tcx.lifetimes.re_erased,
873 ty::TypeAndMut { ty, mutbl: borrow_kind.to_mutbl_lossy() },
874 );
875
876 *region = tcx.lifetimes.re_erased;
877
878 let mut projection = vec![PlaceElem::Deref];
879 projection.extend(place.projection);
9ffffee4 880 place.projection = tcx.mk_place_elems(&projection);
9c376795
FG
881
882 // Create a temp to hold the promoted reference.
883 // This is because `*r` requires `r` to be a local,
884 // otherwise we would use the `promoted` directly.
885 let mut promoted_ref = LocalDecl::new(ref_ty, span);
886 promoted_ref.source_info = statement.source_info;
887 let promoted_ref = local_decls.push(promoted_ref);
888 assert_eq!(self.temps.push(TempState::Unpromotable), promoted_ref);
889
890 let promoted_ref_statement = Statement {
891 source_info: statement.source_info,
892 kind: StatementKind::Assign(Box::new((
893 Place::from(promoted_ref),
894 Rvalue::Use(promoted_operand(ref_ty, span)),
895 ))),
896 };
897 self.extra_statements.push((loc, promoted_ref_statement));
898
899 Rvalue::Ref(
900 tcx.lifetimes.re_erased,
901 *borrow_kind,
902 Place {
903 local: mem::replace(&mut place.local, promoted_ref),
904 projection: List::empty(),
905 },
906 )
a7813a04 907 };
94b46f34
XL
908
909 assert_eq!(self.new_block(), START_BLOCK);
dfeec247
XL
910 self.visit_rvalue(
911 &mut rvalue,
9ffffee4 912 Location { block: START_BLOCK, statement_index: usize::MAX },
dfeec247 913 );
c30ab7b3 914
94b46f34 915 let span = self.promoted.span;
dfeec247 916 self.assign(RETURN_PLACE, rvalue, span);
3c0e092e 917 self.promoted
a7813a04
XL
918 }
919}
920
921/// Replaces all temporaries with their promoted counterparts.
922impl<'a, 'tcx> MutVisitor<'tcx> for Promoter<'a, 'tcx> {
e74abb32
XL
923 fn tcx(&self) -> TyCtxt<'tcx> {
924 self.tcx
925 }
926
dfeec247 927 fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _: Location) {
e74abb32 928 if self.is_temp_kind(*local) {
ea8adc8c 929 *local = self.promote_temp(*local);
a7813a04 930 }
a7813a04
XL
931 }
932}
933
dc9dc135 934pub fn promote_candidates<'tcx>(
f9f354fc 935 body: &mut Body<'tcx>,
dc9dc135
XL
936 tcx: TyCtxt<'tcx>,
937 mut temps: IndexVec<Local, TempState>,
938 candidates: Vec<Candidate>,
f9f354fc 939) -> IndexVec<Promoted, Body<'tcx>> {
a7813a04 940 // Visit candidates in reverse, in case they're nested.
476ff2be 941 debug!("promote_candidates({:?})", candidates);
94b46f34 942
e1599b0c
XL
943 let mut promotions = IndexVec::new();
944
dfeec247 945 let mut extra_statements = vec![];
a7813a04 946 for candidate in candidates.into_iter().rev() {
3c0e092e
XL
947 let Location { block, statement_index } = candidate.location;
948 if let StatementKind::Assign(box (place, _)) = &body[block].statements[statement_index].kind
949 {
950 if let Some(local) = place.as_local() {
951 if temps[local] == TempState::PromotedOut {
952 // Already promoted.
953 continue;
a7813a04 954 }
a7813a04 955 }
94b46f34
XL
956 }
957
dc9dc135 958 // Declare return place local so that `mir::Body::new` doesn't complain.
f9f354fc 959 let initial_locals = iter::once(LocalDecl::new(tcx.types.never, body.span)).collect();
dfeec247 960
3c0e092e 961 let mut scope = body.source_scopes[body.source_info(candidate.location).scope].clone();
29967ef6
XL
962 scope.parent_scope = None;
963
f2b60f7d 964 let mut promoted = Body::new(
29967ef6 965 body.source, // `promoted` gets filled in below
dfeec247 966 IndexVec::new(),
29967ef6 967 IndexVec::from_elem_n(scope, 1),
dfeec247
XL
968 initial_locals,
969 IndexVec::new(),
970 0,
971 vec![],
972 body.span,
6a06907d 973 body.generator_kind(),
5099ac24 974 body.tainted_by_errors,
dfeec247 975 );
f2b60f7d 976 promoted.phase = MirPhase::Analysis(AnalysisPhase::Initial);
c30ab7b3 977
8faf50e0 978 let promoter = Promoter {
f9f354fc 979 promoted,
94b46f34 980 tcx,
dc9dc135 981 source: body,
a7813a04 982 temps: &mut temps,
dfeec247
XL
983 extra_statements: &mut extra_statements,
984 keep_original: false,
a7813a04 985 };
e1599b0c 986
3c0e092e
XL
987 let mut promoted = promoter.promote_candidate(candidate, promotions.len());
988 promoted.source.promoted = Some(promotions.next_index());
989 promotions.push(promoted);
a7813a04
XL
990 }
991
dfeec247
XL
992 // Insert each of `extra_statements` before its indicated location, which
993 // has to be done in reverse location order, to not invalidate the rest.
994 extra_statements.sort_by_key(|&(loc, _)| cmp::Reverse(loc));
995 for (loc, statement) in extra_statements {
996 body[loc.block].statements.insert(loc.statement_index, statement);
997 }
998
a7813a04 999 // Eliminate assignments to, and drops of promoted temps.
c30ab7b3 1000 let promoted = |index: Local| temps[index] == TempState::PromotedOut;
dc9dc135 1001 for block in body.basic_blocks_mut() {
dfeec247
XL
1002 block.statements.retain(|statement| match &statement.kind {
1003 StatementKind::Assign(box (place, _)) => {
1004 if let Some(index) = place.as_local() {
1005 !promoted(index)
1006 } else {
1007 true
a7813a04 1008 }
a7813a04 1009 }
dfeec247
XL
1010 StatementKind::StorageLive(index) | StatementKind::StorageDead(index) => {
1011 !promoted(*index)
1012 }
1013 _ => true,
a7813a04
XL
1014 });
1015 let terminator = block.terminator_mut();
f035d41b 1016 if let TerminatorKind::Drop { place, target, .. } = &terminator.kind {
ba9703b0
XL
1017 if let Some(index) = place.as_local() {
1018 if promoted(index) {
1019 terminator.kind = TerminatorKind::Goto { target: *target };
a7813a04
XL
1020 }
1021 }
a7813a04
XL
1022 }
1023 }
e1599b0c
XL
1024
1025 promotions
a7813a04 1026}
6a06907d
XL
1027
1028/// This function returns `true` if the function being called in the array
1029/// repeat expression is a `const` function.
c295e0f8 1030pub fn is_const_fn_in_array_repeat_expression<'tcx>(
6a06907d
XL
1031 ccx: &ConstCx<'_, 'tcx>,
1032 place: &Place<'tcx>,
1033 body: &Body<'tcx>,
1034) -> bool {
1035 match place.as_local() {
1036 // rule out cases such as: `let my_var = some_fn(); [my_var; N]`
1037 Some(local) if body.local_decls[local].is_user_variable() => return false,
1038 None => return false,
1039 _ => {}
1040 }
1041
f2b60f7d 1042 for block in body.basic_blocks.iter() {
6a06907d
XL
1043 if let Some(Terminator { kind: TerminatorKind::Call { func, destination, .. }, .. }) =
1044 &block.terminator
1045 {
781aab86
FG
1046 if let Operand::Constant(box ConstOperand { const_, .. }) = func {
1047 if let ty::FnDef(def_id, _) = *const_.ty().kind() {
923072b8
FG
1048 if destination == place {
1049 if ccx.tcx.is_const_fn(def_id) {
1050 return true;
6a06907d
XL
1051 }
1052 }
1053 }
1054 }
1055 }
1056 }
1057
1058 false
1059}