]> git.lizzy.rs Git - rust.git/blobdiff - src/librustc_mir/transform/qualify_consts.rs
Auto merge of #63810 - oli-obk:const_offset_from, r=RalfJung,nikic
[rust.git] / src / librustc_mir / transform / qualify_consts.rs
index da1abb9747c1a7ef5cff597176234cc5c8905557..5463b9444731f8c40657f0b156c6ef5609dd3d16 100644 (file)
@@ -206,6 +206,9 @@ fn in_projection_structurally(
                 ProjectionElem::ConstantIndex { .. } |
                 ProjectionElem::Downcast(..) => qualif,
 
+                // FIXME(eddyb) shouldn't this be masked *after* including the
+                // index local? Then again, it's `usize` which is neither
+                // `HasMutInterior` nor `NeedsDrop`.
                 ProjectionElem::Index(local) => qualif || Self::in_local(cx, *local),
             }
         } else {
@@ -292,8 +295,8 @@ fn in_rvalue_structurally(cx: &ConstCx<'_, 'tcx>, rvalue: &Rvalue<'tcx>) -> bool
 
             Rvalue::Ref(_, _, ref place) => {
                 // Special-case reborrows to be more like a copy of the reference.
-                if let box [proj_base @ .., elem] = &place.projection {
-                    if ProjectionElem::Deref == *elem {
+                if let &[ref proj_base @ .., elem] = place.projection.as_ref() {
+                    if ProjectionElem::Deref == elem {
                         let base_ty = Place::ty_from(&place.base, proj_base, cx.body, cx.tcx).ty;
                         if let ty::Ref(..) = base_ty.kind {
                             return Self::in_place(cx, PlaceRef {
@@ -442,6 +445,7 @@ fn in_static(cx: &ConstCx<'_, 'tcx>, static_: &Static<'tcx>) -> bool {
             StaticKind::Promoted(_, _) => unreachable!(),
             StaticKind::Static => {
                 // Only allow statics (not consts) to refer to other statics.
+                // FIXME(eddyb) does this matter at all for promotion?
                 let allowed = cx.mode == Mode::Static || cx.mode == Mode::StaticMut;
 
                 !allowed ||
@@ -560,6 +564,7 @@ fn in_call(
                             | "transmute"
                             | "simd_insert"
                             | "simd_extract"
+                            | "ptr_offset_from"
                             => return true,
 
                             _ => {}
@@ -674,6 +679,7 @@ struct Checker<'a, 'tcx> {
 
     temp_promotion_state: IndexVec<Local, TempState>,
     promotion_candidates: Vec<Candidate>,
+    unchecked_promotion_candidates: Vec<Candidate>,
 
     /// If `true`, do not emit errors to the user, merely collect them in `errors`.
     suppress_errors: bool,
@@ -703,7 +709,8 @@ impl<'a, 'tcx> Checker<'a, 'tcx> {
     fn new(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>, mode: Mode) -> Self {
         assert!(def_id.is_local());
         let mut rpo = traversal::reverse_postorder(body);
-        let temps = promote_consts::collect_temps(body, &mut rpo);
+        let (temps, unchecked_promotion_candidates) =
+            promote_consts::collect_temps_and_candidates(tcx, body, &mut rpo);
         rpo.reset();
 
         let param_env = tcx.param_env(def_id);
@@ -742,6 +749,7 @@ fn new(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>, mode: Mode) -> Se
             rpo,
             temp_promotion_state: temps,
             promotion_candidates: vec![],
+            unchecked_promotion_candidates,
             errors: vec![],
             suppress_errors: false,
         }
@@ -828,6 +836,10 @@ fn assign(&mut self, dest: &Place<'tcx>, source: ValueSource<'_, 'tcx>, location
                 } else if let BorrowKind::Mut { .. } | BorrowKind::Shared = kind {
                     // Don't promote BorrowKind::Shallow borrows, as they don't
                     // reach codegen.
+                    // FIXME(eddyb) the two other kinds of borrow (`Shallow` and `Unique`)
+                    // aren't promoted here but *could* be promoted as part of a larger
+                    // value because `IsNotPromotable` isn't being set for them,
+                    // need to figure out what is the intended behavior.
 
                     // We might have a candidate for promotion.
                     let candidate = Candidate::Ref(location);
@@ -867,13 +879,11 @@ fn assign(&mut self, dest: &Place<'tcx>, source: ValueSource<'_, 'tcx>, location
                 }
             },
             ValueSource::Rvalue(&Rvalue::Repeat(ref operand, _)) => {
-                let candidate = Candidate::Repeat(location);
-                let not_promotable = IsNotImplicitlyPromotable::in_operand(self, operand) ||
-                                     IsNotPromotable::in_operand(self, operand);
-                debug!("assign: self.def_id={:?} operand={:?}", self.def_id, operand);
-                if !not_promotable && self.tcx.features().const_in_array_repeat_expressions {
-                    debug!("assign: candidate={:?}", candidate);
-                    self.promotion_candidates.push(candidate);
+                debug!("assign: self.cx.mode={:?} self.def_id={:?} location={:?} operand={:?}",
+                       self.cx.mode, self.def_id, location, operand);
+                if self.should_promote_repeat_expression(operand) &&
+                        self.tcx.features().const_in_array_repeat_expressions {
+                    self.promotion_candidates.push(Candidate::Repeat(location));
                 }
             },
             _ => {},
@@ -957,8 +967,7 @@ fn check_const(&mut self) -> (u8, &'tcx BitSet<Local>) {
         }
 
         let item = new_checker::Item::new(self.tcx, self.def_id, self.body);
-        let mut_borrowed_locals = new_checker::validation::compute_indirectly_mutable_locals(&item);
-        let mut validator = new_checker::validation::Validator::new(&item, &mut_borrowed_locals);
+        let mut validator = new_checker::validation::Validator::new(&item);
 
         validator.suppress_errors = !use_new_validator;
         self.suppress_errors = use_new_validator;
@@ -967,6 +976,7 @@ fn check_const(&mut self) -> (u8, &'tcx BitSet<Local>) {
 
         let mut seen_blocks = BitSet::new_empty(body.basic_blocks().len());
         let mut bb = START_BLOCK;
+        let mut has_controlflow_error = false;
         loop {
             seen_blocks.insert(bb.index());
 
@@ -1007,6 +1017,7 @@ fn check_const(&mut self) -> (u8, &'tcx BitSet<Local>) {
                     bb = target;
                 }
                 _ => {
+                    has_controlflow_error = true;
                     self.not_const(ops::Loop);
                     validator.check_op(ops::Loop);
                     break;
@@ -1036,31 +1047,48 @@ fn check_const(&mut self) -> (u8, &'tcx BitSet<Local>) {
         // Collect all the temps we need to promote.
         let mut promoted_temps = BitSet::new_empty(self.temp_promotion_state.len());
 
-        debug!("qualify_const: promotion_candidates={:?}", self.promotion_candidates);
-        for candidate in &self.promotion_candidates {
-            match *candidate {
+        // HACK(eddyb) don't try to validate promotion candidates if any
+        // parts of the control-flow graph were skipped due to an error.
+        let promotion_candidates = if has_controlflow_error {
+            let unleash_miri = self
+                .tcx
+                .sess
+                .opts
+                .debugging_opts
+                .unleash_the_miri_inside_of_you;
+            if !unleash_miri {
+                self.tcx.sess.delay_span_bug(
+                    body.span,
+                    "check_const: expected control-flow error(s)",
+                );
+            }
+            self.promotion_candidates.clone()
+        } else {
+            self.valid_promotion_candidates()
+        };
+        debug!("qualify_const: promotion_candidates={:?}", promotion_candidates);
+        for candidate in promotion_candidates {
+            match candidate {
                 Candidate::Repeat(Location { block: bb, statement_index: stmt_idx }) => {
                     if let StatementKind::Assign(box(_, Rvalue::Repeat(
-                        Operand::Move(Place {
-                            base: PlaceBase::Local(index),
-                            projection: box [],
-                        }),
+                        Operand::Move(place),
                         _
-                    ))) = self.body[bb].statements[stmt_idx].kind {
-                        promoted_temps.insert(index);
+                    ))) = &self.body[bb].statements[stmt_idx].kind {
+                        if let Some(index) = place.as_local() {
+                            promoted_temps.insert(index);
+                        }
                     }
                 }
                 Candidate::Ref(Location { block: bb, statement_index: stmt_idx }) => {
                     if let StatementKind::Assign(
                         box(
                             _,
-                            Rvalue::Ref(_, _, Place {
-                                base: PlaceBase::Local(index),
-                                projection: box [],
-                            })
+                            Rvalue::Ref(_, _, place)
                         )
-                    ) = self.body[bb].statements[stmt_idx].kind {
-                        promoted_temps.insert(index);
+                    ) = &self.body[bb].statements[stmt_idx].kind {
+                        if let Some(index) = place.as_local() {
+                            promoted_temps.insert(index);
+                        }
                     }
                 }
                 Candidate::Argument { .. } => {}
@@ -1077,6 +1105,58 @@ fn check_const(&mut self) -> (u8, &'tcx BitSet<Local>) {
 
         (qualifs.encode_to_bits(), self.tcx.arena.alloc(promoted_temps))
     }
+
+    /// Get the subset of `unchecked_promotion_candidates` that are eligible
+    /// for promotion.
+    // FIXME(eddyb) replace the old candidate gathering with this.
+    fn valid_promotion_candidates(&self) -> Vec<Candidate> {
+        // Sanity-check the promotion candidates.
+        let candidates = promote_consts::validate_candidates(
+            self.tcx,
+            self.body,
+            self.def_id,
+            &self.temp_promotion_state,
+            &self.unchecked_promotion_candidates,
+        );
+
+        if candidates != self.promotion_candidates {
+            let report = |msg, candidate| {
+                let span = match candidate {
+                    Candidate::Ref(loc) |
+                    Candidate::Repeat(loc) => self.body.source_info(loc).span,
+                    Candidate::Argument { bb, .. } => {
+                        self.body[bb].terminator().source_info.span
+                    }
+                };
+                self.tcx.sess.span_err(span, &format!("{}: {:?}", msg, candidate));
+            };
+
+            for &c in &self.promotion_candidates {
+                if !candidates.contains(&c) {
+                    report("invalidated old candidate", c);
+                }
+            }
+
+            for &c in &candidates {
+                if !self.promotion_candidates.contains(&c) {
+                    report("extra new candidate", c);
+                }
+            }
+
+            bug!("promotion candidate validation mismatches (see above)");
+        }
+
+        candidates
+    }
+
+    /// Returns `true` if the operand of a repeat expression is promotable.
+    fn should_promote_repeat_expression(&self, operand: &Operand<'tcx>) -> bool {
+        let not_promotable = IsNotImplicitlyPromotable::in_operand(self, operand) ||
+                             IsNotPromotable::in_operand(self, operand);
+        debug!("should_promote_repeat_expression: operand={:?} not_promotable={:?}",
+               operand, not_promotable);
+        !not_promotable
+    }
 }
 
 impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
@@ -1237,10 +1317,7 @@ fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
         match *operand {
             Operand::Move(ref place) => {
                 // Mark the consumed locals to indicate later drops are noops.
-                if let Place {
-                    base: PlaceBase::Local(local),
-                    projection: box [],
-                } = *place {
+                if let Some(local) = place.as_local() {
                     self.cx.per_local[NeedsDrop].remove(local);
                 }
             }
@@ -1256,8 +1333,8 @@ fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
         if let Rvalue::Ref(_, kind, ref place) = *rvalue {
             // Special-case reborrows.
             let mut reborrow_place = None;
-            if let box [proj_base @ .., elem] = &place.projection {
-                if *elem == ProjectionElem::Deref {
+            if let &[ref proj_base @ .., elem] = place.projection.as_ref() {
+                if elem == ProjectionElem::Deref {
                     let base_ty = Place::ty_from(&place.base, proj_base, self.body, self.tcx).ty;
                     if let ty::Ref(..) = base_ty.kind {
                         reborrow_place = Some(proj_base);
@@ -1530,20 +1607,12 @@ fn visit_terminator_kind(&mut self,
                     // This is not a problem, because the argument explicitly
                     // requests constness, in contrast to regular promotion
                     // which happens even without the user requesting it.
-                    // We can error out with a hard error if the argument is not
-                    // constant here.
+                    //
+                    // `promote_consts` is responsible for emitting the error if
+                    // the argument is not promotable.
                     if !IsNotPromotable::in_operand(self, arg) {
                         debug!("visit_terminator_kind: candidate={:?}", candidate);
                         self.promotion_candidates.push(candidate);
-                    } else {
-                        if is_shuffle {
-                            span_err!(self.tcx.sess, self.span, E0526,
-                                      "shuffle indices are not constant");
-                        } else {
-                            self.tcx.sess.span_err(self.span,
-                                &format!("argument {} is required to be a constant",
-                                         i + 1));
-                        }
                     }
                 }
             }
@@ -1568,10 +1637,7 @@ fn visit_terminator_kind(&mut self,
                 unleash_miri!(self);
                 // HACK(eddyb): emulate a bit of dataflow analysis,
                 // conservatively, that drop elaboration will do.
-                let needs_drop = if let Place {
-                    base: PlaceBase::Local(local),
-                    projection: box [],
-                } = *place {
+                let needs_drop = if let Some(local) = place.as_local() {
                     if NeedsDrop::in_local(self, local) {
                         Some(self.body.local_decls[local].source_info.span)
                     } else {
@@ -1652,6 +1718,10 @@ pub fn provide(providers: &mut Providers<'_>) {
     };
 }
 
+// FIXME(eddyb) this is only left around for the validation logic
+// in `promote_consts`, see the comment in `validate_operand`.
+pub(super) const QUALIF_ERROR_BIT: u8 = 1 << IsNotPromotable::IDX;
+
 fn mir_const_qualif(tcx: TyCtxt<'_>, def_id: DefId) -> (u8, &BitSet<Local>) {
     // N.B., this `borrow()` is guaranteed to be valid (i.e., the value
     // cannot yet be stolen), because `mir_validated()`, which steals
@@ -1661,7 +1731,7 @@ fn mir_const_qualif(tcx: TyCtxt<'_>, def_id: DefId) -> (u8, &BitSet<Local>) {
 
     if body.return_ty().references_error() {
         tcx.sess.delay_span_bug(body.span, "mir_const_qualif: MIR had errors");
-        return (1 << IsNotPromotable::IDX, tcx.arena.alloc(BitSet::new_empty(0)));
+        return (QUALIF_ERROR_BIT, tcx.arena.alloc(BitSet::new_empty(0)));
     }
 
     Checker::new(tcx, def_id, body, Mode::Const).check_const()
@@ -1703,29 +1773,33 @@ fn run_pass(&self, tcx: TyCtxt<'tcx>, src: MirSource<'tcx>, body: &mut Body<'tcx
             let (temps, candidates) = {
                 let mut checker = Checker::new(tcx, def_id, body, mode);
                 if let Mode::ConstFn = mode {
-                    if tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
-                        checker.check_const();
-                    } else if tcx.is_min_const_fn(def_id) {
+                    let use_min_const_fn_checks =
+                        !tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you &&
+                        tcx.is_min_const_fn(def_id);
+                    if use_min_const_fn_checks {
                         // Enforce `min_const_fn` for stable `const fn`s.
                         use super::qualify_min_const_fn::is_min_const_fn;
                         if let Err((span, err)) = is_min_const_fn(tcx, def_id, body) {
                             error_min_const_fn_violation(tcx, span, err);
-                        } else {
-                            // this should not produce any errors, but better safe than sorry
-                            // FIXME(#53819)
-                            checker.check_const();
+                            return;
                         }
-                    } else {
-                        // Enforce a constant-like CFG for `const fn`.
-                        checker.check_const();
+
+                        // `check_const` should not produce any errors, but better safe than sorry
+                        // FIXME(#53819)
+                        // NOTE(eddyb) `check_const` is actually needed for promotion inside
+                        // `min_const_fn` functions.
                     }
+
+                    // Enforce a constant-like CFG for `const fn`.
+                    checker.check_const();
                 } else {
                     while let Some((bb, data)) = checker.rpo.next() {
                         checker.visit_basic_block_data(bb, data);
                     }
                 }
 
-                (checker.temp_promotion_state, checker.promotion_candidates)
+                let promotion_candidates = checker.valid_promotion_candidates();
+                (checker.temp_promotion_state, promotion_candidates)
             };
 
             // Do the actual promotion, now that we know what's viable.
@@ -1817,16 +1891,17 @@ fn remove_drop_and_storage_dead_on_promoted_locals(
             }
         });
         let terminator = block.terminator_mut();
-        match terminator.kind {
+        match &terminator.kind {
             TerminatorKind::Drop {
-                location: Place {
-                    base: PlaceBase::Local(index),
-                    projection: box [],
-                },
+                location,
                 target,
                 ..
-            } if promoted_temps.contains(index) => {
-                terminator.kind = TerminatorKind::Goto { target };
+            } => {
+                if let Some(index) = location.as_local() {
+                    if promoted_temps.contains(index) {
+                        terminator.kind = TerminatorKind::Goto { target: *target };
+                    }
+                }
             }
             _ => {}
         }