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 {
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 {
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 ||
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,
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);
rpo,
temp_promotion_state: temps,
promotion_candidates: vec![],
+ unchecked_promotion_candidates,
errors: vec![],
suppress_errors: false,
}
} 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);
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());
bb = target;
}
_ => {
+ has_controlflow_error = true;
self.not_const(ops::Loop);
validator.check_op(ops::Loop);
break;
new_errors.dedup();
if self.errors != new_errors {
- error!("old validator: {:?}", self.errors);
- error!("new validator: {:?}", new_errors);
-
- // ICE on nightly if the validators do not emit exactly the same errors.
- // Users can supress this panic with an unstable compiler flag (hopefully after
- // filing an issue).
- let opts = &self.tcx.sess.opts;
- let trigger_ice = opts.unstable_features.is_nightly_build()
- && !opts.debugging_opts.suppress_const_validation_back_compat_ice;
-
- if trigger_ice {
- span_bug!(
- body.span,
- "{}",
- VALIDATOR_MISMATCH_ERR,
- );
- }
+ validator_mismatch(
+ self.tcx,
+ body,
+ std::mem::replace(&mut self.errors, vec![]),
+ new_errors,
+ );
}
}
// 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 { .. } => {}
(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.per_local.0[HasMutInterior::IDX],
+ &self.per_local.0[NeedsDrop::IDX],
+ &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
+ }
}
impl<'a, 'tcx> Visitor<'tcx> for Checker<'a, 'tcx> {
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);
}
}
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);
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 {
};
}
+// 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
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()
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.
}
});
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 };
+ }
+ }
}
_ => {}
}
Some(ret)
}
+fn validator_mismatch(
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ mut old_errors: Vec<(Span, String)>,
+ mut new_errors: Vec<(Span, String)>,
+) {
+ error!("old validator: {:?}", old_errors);
+ error!("new validator: {:?}", new_errors);
+
+ // ICE on nightly if the validators do not emit exactly the same errors.
+ // Users can supress this panic with an unstable compiler flag (hopefully after
+ // filing an issue).
+ let opts = &tcx.sess.opts;
+ let strict_validation_enabled = opts.unstable_features.is_nightly_build()
+ && !opts.debugging_opts.suppress_const_validation_back_compat_ice;
+
+ if !strict_validation_enabled {
+ return;
+ }
+
+ // If this difference would cause a regression from the old to the new or vice versa, trigger
+ // the ICE.
+ if old_errors.is_empty() || new_errors.is_empty() {
+ span_bug!(body.span, "{}", VALIDATOR_MISMATCH_ERR);
+ }
+
+ // HACK: Borrows that would allow mutation are forbidden in const contexts, but they cause the
+ // new validator to be more conservative about when a dropped local has been moved out of.
+ //
+ // Supress the mismatch ICE in cases where the validators disagree only on the number of
+ // `LiveDrop` errors and both observe the same sequence of `MutBorrow`s.
+
+ let is_live_drop = |(_, s): &mut (_, String)| s.starts_with("LiveDrop");
+ let is_mut_borrow = |(_, s): &&(_, String)| s.starts_with("MutBorrow");
+
+ let old_live_drops: Vec<_> = old_errors.drain_filter(is_live_drop).collect();
+ let new_live_drops: Vec<_> = new_errors.drain_filter(is_live_drop).collect();
+
+ let only_live_drops_differ = old_live_drops != new_live_drops && old_errors == new_errors;
+
+ let old_mut_borrows = old_errors.iter().filter(is_mut_borrow);
+ let new_mut_borrows = new_errors.iter().filter(is_mut_borrow);
+
+ let at_least_one_mut_borrow = old_mut_borrows.clone().next().is_some();
+
+ if only_live_drops_differ && at_least_one_mut_borrow && old_mut_borrows.eq(new_mut_borrows) {
+ return;
+ }
+
+ span_bug!(body.span, "{}", VALIDATOR_MISMATCH_ERR);
+}
+
const VALIDATOR_MISMATCH_ERR: &str =
r"Disagreement between legacy and dataflow-based const validators.
After filing an issue, use `-Zsuppress-const-validation-back-compat-ice` to compile your code.";