Auto merge of #72010 - Dylan-DPC:rollup-prdj0pk, r=Dylan-DPC

Rollup of 6 pull requests

Successful merges:

 - #71989 (Use a single enum for the kind of a const context)
 - #71993 (Remove old `util/liveness.rs` module)
 - #71999 (Add myself to mailmap.)
 - #72001 (Adjust cfg(version) to lang team decision)
 - #72007 (Fix some tests failing in `--pass check` mode)
 - #72008 (Add const-generics test)

Failed merges:

r? @ghost
This commit is contained in:
bors 2020-05-08 13:11:43 +00:00
commit 7b805396bf
24 changed files with 235 additions and 476 deletions

View File

@ -49,6 +49,7 @@ Carol (Nichols || Goulding) <carol.nichols@gmail.com> <193874+carols10cents@user
Carol (Nichols || Goulding) <carol.nichols@gmail.com> <carol.nichols@gmail.com>
Carol (Nichols || Goulding) <carol.nichols@gmail.com> <cnichols@thinkthroughmath.com>
Carol Willing <carolcode@willingconsulting.com>
Charles Lew <crlf0710@gmail.com> CrLF0710 <crlf0710@gmail.com>
Chris C Cerami <chrisccerami@users.noreply.github.com> Chris C Cerami <chrisccerami@gmail.com>
Chris Pressey <cpressey@gmail.com>
Chris Thorn <chris@thorn.co> Chris Thorn <thorn@thoughtbot.com>

View File

@ -1,4 +1,5 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=CFG_VERSION");
println!("cargo:rerun-if-env-changed=CFG_RELEASE");
println!("cargo:rerun-if-env-changed=CFG_RELEASE_CHANNEL");
}

View File

@ -652,9 +652,12 @@ pub fn eval_condition(
return false;
}
};
let version = Version::parse(env!("CFG_VERSION")).unwrap();
let channel = env!("CFG_RELEASE_CHANNEL");
let nightly = channel == "nightly" || channel == "dev";
let rustc_version = Version::parse(env!("CFG_RELEASE")).unwrap();
version >= min_version
// See https://github.com/rust-lang/rust/issues/64796#issuecomment-625474439 for details
if nightly { rustc_version > min_version } else { rustc_version >= min_version }
}
ast::MetaItemKind::List(ref mis) => {
for mi in mis.iter() {

View File

@ -1291,6 +1291,53 @@ impl BodyOwnerKind {
}
}
/// The kind of an item that requires const-checking.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ConstContext {
/// A `const fn`.
ConstFn,
/// A `static` or `static mut`.
Static(Mutability),
/// A `const`, associated `const`, or other const context.
///
/// Other contexts include:
/// - Array length expressions
/// - Enum discriminants
/// - Const generics
///
/// For the most part, other contexts are treated just like a regular `const`, so they are
/// lumped into the same category.
Const,
}
impl ConstContext {
/// A description of this const context that can appear between backticks in an error message.
///
/// E.g. `const` or `static mut`.
pub fn keyword_name(self) -> &'static str {
match self {
Self::Const => "const",
Self::Static(Mutability::Not) => "static",
Self::Static(Mutability::Mut) => "static mut",
Self::ConstFn => "const fn",
}
}
}
/// A colloquial, trivially pluralizable description of this const context for use in error
/// messages.
impl fmt::Display for ConstContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Const => write!(f, "constant"),
Self::Static(_) => write!(f, "static"),
Self::ConstFn => write!(f, "constant function"),
}
}
}
/// A literal.
pub type Lit = Spanned<LitKind>;

View File

@ -408,6 +408,9 @@ impl<'hir> Map<'hir> {
})
}
/// Returns the `BodyOwnerKind` of this `LocalDefId`.
///
/// Panics if `LocalDefId` does not have an associated body.
pub fn body_owner_kind(&self, id: HirId) -> BodyOwnerKind {
match self.get(id) {
Node::Item(&Item { kind: ItemKind::Const(..), .. })
@ -424,6 +427,23 @@ impl<'hir> Map<'hir> {
}
}
/// Returns the `ConstContext` of the body associated with this `LocalDefId`.
///
/// Panics if `LocalDefId` does not have an associated body.
pub fn body_const_context(&self, did: LocalDefId) -> Option<ConstContext> {
let hir_id = self.local_def_id_to_hir_id(did);
let ccx = match self.body_owner_kind(hir_id) {
BodyOwnerKind::Const => ConstContext::Const,
BodyOwnerKind::Static(mt) => ConstContext::Static(mt),
BodyOwnerKind::Fn if self.tcx.is_constructor(did.to_def_id()) => return None,
BodyOwnerKind::Fn if self.tcx.is_const_fn_raw(did.to_def_id()) => ConstContext::ConstFn,
BodyOwnerKind::Fn | BodyOwnerKind::Closure => return None,
};
Some(ccx)
}
pub fn ty_param_owner(&self, id: HirId) -> HirId {
match self.get(id) {
Node::Item(&Item { kind: ItemKind::Trait(..) | ItemKind::TraitAlias(..), .. }) => id,

View File

@ -0,0 +1,78 @@
use rustc_middle::mir::visit::{
MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext,
};
#[derive(Eq, PartialEq, Clone)]
pub enum DefUse {
Def,
Use,
Drop,
}
pub fn categorize(context: PlaceContext) -> Option<DefUse> {
match context {
///////////////////////////////////////////////////////////////////////////
// DEFS
PlaceContext::MutatingUse(MutatingUseContext::Store) |
// This is potentially both a def and a use...
PlaceContext::MutatingUse(MutatingUseContext::AsmOutput) |
// We let Call define the result in both the success and
// unwind cases. This is not really correct, however it
// does not seem to be observable due to the way that we
// generate MIR. To do things properly, we would apply
// the def in call only to the input from the success
// path and not the unwind path. -nmatsakis
PlaceContext::MutatingUse(MutatingUseContext::Call) |
PlaceContext::MutatingUse(MutatingUseContext::Yield) |
// Storage live and storage dead aren't proper defines, but we can ignore
// values that come before them.
PlaceContext::NonUse(NonUseContext::StorageLive) |
PlaceContext::NonUse(NonUseContext::StorageDead) => Some(DefUse::Def),
///////////////////////////////////////////////////////////////////////////
// REGULAR USES
//
// These are uses that occur *outside* of a drop. For the
// purposes of NLL, these are special in that **all** the
// lifetimes appearing in the variable must be live for each regular use.
PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) |
PlaceContext::MutatingUse(MutatingUseContext::Projection) |
// Borrows only consider their local used at the point of the borrow.
// This won't affect the results since we use this analysis for generators
// and we only care about the result at suspension points. Borrows cannot
// cross suspension points so this behavior is unproblematic.
PlaceContext::MutatingUse(MutatingUseContext::Borrow) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::UniqueBorrow) |
PlaceContext::MutatingUse(MutatingUseContext::AddressOf) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::AddressOf) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::Inspect) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) |
PlaceContext::NonUse(NonUseContext::AscribeUserTy) |
PlaceContext::MutatingUse(MutatingUseContext::Retag) =>
Some(DefUse::Use),
///////////////////////////////////////////////////////////////////////////
// DROP USES
//
// These are uses that occur in a DROP (a MIR drop, not a
// call to `std::mem::drop()`). For the purposes of NLL,
// uses in drop are special because `#[may_dangle]`
// attributes can affect whether lifetimes must be live.
PlaceContext::MutatingUse(MutatingUseContext::Drop) =>
Some(DefUse::Drop),
// Debug info is neither def nor use.
PlaceContext::NonUse(NonUseContext::VarDebugInfo) => None,
}
}

View File

@ -2,10 +2,10 @@ use std::collections::VecDeque;
use std::rc::Rc;
use crate::borrow_check::{
def_use::{self, DefUse},
nll::ToRegionVid,
region_infer::{Cause, RegionInferenceContext},
};
use crate::util::liveness::{self, DefUse};
use rustc_data_structures::fx::FxHashSet;
use rustc_middle::mir::visit::{MirVisitable, PlaceContext, Visitor};
use rustc_middle::mir::{Body, Local, Location};
@ -117,7 +117,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for DefUseVisitor<'cx, 'tcx> {
});
if found_it {
self.def_use_result = match liveness::categorize(context) {
self.def_use_result = match def_use::categorize(context) {
Some(DefUse::Def) => Some(DefUseResult::Def),
Some(DefUse::Use) => Some(DefUseResult::UseLive { local }),
Some(DefUse::Drop) => Some(DefUseResult::UseDrop { local }),

View File

@ -51,6 +51,7 @@ use self::path_utils::*;
mod borrow_set;
mod constraint_generation;
mod constraints;
mod def_use;
mod diagnostics;
mod facts;
mod invalidation;

View File

@ -3,8 +3,7 @@ use rustc_index::vec::IndexVec;
use rustc_middle::mir::visit::{PlaceContext, Visitor};
use rustc_middle::mir::{Body, Local, Location};
use crate::util::liveness::{categorize, DefUse};
use crate::borrow_check::def_use::{self, DefUse};
use crate::borrow_check::region_infer::values::{PointIndex, RegionValueElements};
/// A map that cross references each local with the locations where it
@ -160,7 +159,7 @@ impl LocalUseMapBuild<'_> {
impl Visitor<'tcx> for LocalUseMapBuild<'_> {
fn visit_local(&mut self, &local: &Local, context: PlaceContext, location: Location) {
if self.locals_with_use_data[local] {
match categorize(context) {
match def_use::categorize(context) {
Some(DefUse::Def) => self.insert_def(local, location),
Some(DefUse::Use) => self.insert_use(local, location),
Some(DefUse::Drop) => self.insert_drop(local, location),

View File

@ -1,7 +1,7 @@
use crate::borrow_check::def_use::{self, DefUse};
use crate::borrow_check::location::{LocationIndex, LocationTable};
use crate::dataflow::indexes::MovePathIndex;
use crate::dataflow::move_paths::{LookupResult, MoveData};
use crate::util::liveness::{categorize, DefUse};
use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::{Body, Local, Location, Place};
use rustc_middle::ty::subst::GenericArg;
@ -56,7 +56,7 @@ impl UseFactsExtractor<'_> {
impl Visitor<'tcx> for UseFactsExtractor<'_> {
fn visit_local(&mut self, &local: &Local, context: PlaceContext, location: Location) {
match categorize(context) {
match def_use::categorize(context) {
Some(DefUse::Def) => self.insert_def(local, location),
Some(DefUse::Use) => self.insert_use(local, location),
Some(DefUse::Drop) => self.insert_drop_use(local, location),

View File

@ -6,6 +6,13 @@ use crate::dataflow::{AnalysisDomain, Backward, BottomValue, GenKill, GenKillAna
/// A [live-variable dataflow analysis][liveness].
///
/// This analysis considers references as being used only at the point of the
/// borrow. In other words, this analysis does not track uses because of references that already
/// exist. See [this `mir-datalow` test][flow-test] for an example. You almost never want to use
/// this analysis without also looking at the results of [`MaybeBorrowedLocals`].
///
/// [`MaybeBorrowedLocals`]: ../struct.MaybeBorrowedLocals.html
/// [flow-test]: https://github.com/rust-lang/rust/blob/a08c47310c7d49cbdc5d7afb38408ba519967ecd/src/test/ui/mir-dataflow/liveness-ptr.rs
/// [liveness]: https://en.wikipedia.org/wiki/Live_variable_analysis
pub struct MaybeLiveLocals;

View File

@ -9,8 +9,6 @@ use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_middle::mir;
use rustc_middle::ty::{self, TyCtxt};
use std::fmt;
pub use self::qualifs::Qualif;
mod ops;
@ -25,7 +23,7 @@ pub struct ConstCx<'mir, 'tcx> {
pub tcx: TyCtxt<'tcx>,
pub def_id: DefId,
pub param_env: ty::ParamEnv<'tcx>,
pub const_kind: Option<ConstKind>,
pub const_kind: Option<hir::ConstContext>,
}
impl ConstCx<'mir, 'tcx> {
@ -40,78 +38,18 @@ impl ConstCx<'mir, 'tcx> {
body: &'mir mir::Body<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> Self {
let const_kind = ConstKind::for_item(tcx, def_id);
let const_kind = tcx.hir().body_const_context(def_id);
ConstCx { body, tcx, def_id: def_id.to_def_id(), param_env, const_kind }
}
/// Returns the kind of const context this `Item` represents (`const`, `static`, etc.).
///
/// Panics if this `Item` is not const.
pub fn const_kind(&self) -> ConstKind {
pub fn const_kind(&self) -> hir::ConstContext {
self.const_kind.expect("`const_kind` must not be called on a non-const fn")
}
}
/// The kinds of items which require compile-time evaluation.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ConstKind {
/// A `static` item.
Static,
/// A `static mut` item.
StaticMut,
/// A `const fn` item.
ConstFn,
/// A `const` item or an anonymous constant (e.g. in array lengths).
Const,
}
impl ConstKind {
/// Returns the validation mode for the item with the given `DefId`, or `None` if this item
/// does not require validation (e.g. a non-const `fn`).
pub fn for_item(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Option<Self> {
use hir::BodyOwnerKind as HirKind;
let hir_id = tcx.hir().as_local_hir_id(def_id);
let mode = match tcx.hir().body_owner_kind(hir_id) {
HirKind::Closure => return None,
// Note: this is deliberately checking for `is_const_fn_raw`, as the `is_const_fn`
// checks take into account the `rustc_const_unstable` attribute combined with enabled
// feature gates. Otherwise, const qualification would _not check_ whether this
// function body follows the `const fn` rules, as an unstable `const fn` would
// be considered "not const". More details are available in issue #67053.
HirKind::Fn if tcx.is_const_fn_raw(def_id) => ConstKind::ConstFn,
HirKind::Fn => return None,
HirKind::Const => ConstKind::Const,
HirKind::Static(hir::Mutability::Not) => ConstKind::Static,
HirKind::Static(hir::Mutability::Mut) => ConstKind::StaticMut,
};
Some(mode)
}
pub fn is_static(self) -> bool {
match self {
ConstKind::Static | ConstKind::StaticMut => true,
ConstKind::ConstFn | ConstKind::Const => false,
}
}
}
impl fmt::Display for ConstKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ConstKind::Const => write!(f, "constant"),
ConstKind::Static | ConstKind::StaticMut => write!(f, "static"),
ConstKind::ConstFn => write!(f, "constant function"),
}
}
}
/// Returns `true` if this `DefId` points to one of the official `panic` lang items.
pub fn is_lang_panic_fn(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool {
Some(def_id) == tcx.lang_items().panic_fn() || Some(def_id) == tcx.lang_items().begin_panic_fn()

View File

@ -1,13 +1,14 @@
//! Concrete error types for all operations which may be invalid in a certain const context.
use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_session::config::nightly_options;
use rustc_session::parse::feature_err;
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol};
use super::{ConstCx, ConstKind};
use super::ConstCx;
/// An operation that is not *always* allowed in a const context.
pub trait NonConstOp: std::fmt::Debug {
@ -326,7 +327,7 @@ impl NonConstOp for RawPtrToIntCast {
pub struct StaticAccess;
impl NonConstOp for StaticAccess {
fn is_allowed_in_item(&self, ccx: &ConstCx<'_, '_>) -> bool {
ccx.const_kind().is_static()
matches!(ccx.const_kind(), hir::ConstContext::Static(_))
}
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@ -374,7 +375,7 @@ pub struct UnionAccess;
impl NonConstOp for UnionAccess {
fn is_allowed_in_item(&self, ccx: &ConstCx<'_, '_>) -> bool {
// Union accesses are stable in all contexts except `const fn`.
ccx.const_kind() != ConstKind::ConstFn
ccx.const_kind() != hir::ConstContext::ConstFn
|| ccx.tcx.features().enabled(Self::feature_gate().unwrap())
}

View File

@ -1,7 +1,7 @@
//! The `Visitor` responsible for actually checking a `mir::Body` for invalid operations.
use rustc_errors::struct_span_err;
use rustc_hir::lang_items;
use rustc_hir::{self as hir, lang_items};
use rustc_hir::{def_id::DefId, HirId};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
@ -18,7 +18,7 @@ use std::ops::Deref;
use super::ops::{self, NonConstOp};
use super::qualifs::{self, CustomEq, HasMutInterior, NeedsDrop};
use super::resolver::FlowSensitiveAnalysis;
use super::{is_lang_panic_fn, ConstCx, ConstKind, Qualif};
use super::{is_lang_panic_fn, ConstCx, Qualif};
use crate::const_eval::{is_const_fn, is_unstable_const_fn};
use crate::dataflow::impls::MaybeMutBorrowedLocals;
use crate::dataflow::{self, Analysis};
@ -145,17 +145,13 @@ impl Qualifs<'mir, 'tcx> {
// We don't care whether a `const fn` returns a value that is not structurally
// matchable. Functions calls are opaque and always use type-based qualification, so
// this value should never be used.
ConstKind::ConstFn => true,
hir::ConstContext::ConstFn => true,
// If we know that all values of the return type are structurally matchable, there's no
// need to run dataflow.
ConstKind::Const | ConstKind::Static | ConstKind::StaticMut
if !CustomEq::in_any_value_of_ty(ccx, ccx.body.return_ty()) =>
{
false
}
_ if !CustomEq::in_any_value_of_ty(ccx, ccx.body.return_ty()) => false,
ConstKind::Const | ConstKind::Static | ConstKind::StaticMut => {
hir::ConstContext::Const | hir::ConstContext::Static(_) => {
let mut cursor = FlowSensitiveAnalysis::new(CustomEq, ccx)
.into_engine(ccx.tcx, &ccx.body, ccx.def_id)
.iterate_to_fixpoint()
@ -198,7 +194,7 @@ impl Validator<'mir, 'tcx> {
pub fn check_body(&mut self) {
let ConstCx { tcx, body, def_id, const_kind, .. } = *self.ccx;
let use_min_const_fn_checks = (const_kind == Some(ConstKind::ConstFn)
let use_min_const_fn_checks = (const_kind == Some(hir::ConstContext::ConstFn)
&& crate::const_eval::is_min_const_fn(tcx, def_id))
&& !tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you;
@ -222,8 +218,9 @@ impl Validator<'mir, 'tcx> {
self.visit_body(&body);
// Ensure that the end result is `Sync` in a non-thread local `static`.
let should_check_for_sync =
const_kind == Some(ConstKind::Static) && !tcx.is_thread_local_static(def_id);
let should_check_for_sync = const_kind
== Some(hir::ConstContext::Static(hir::Mutability::Not))
&& !tcx.is_thread_local_static(def_id);
if should_check_for_sync {
let hir_id = tcx.hir().as_local_hir_id(def_id.expect_local());
@ -351,7 +348,9 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> {
let ty = place.ty(self.body, self.tcx).ty;
let is_allowed = match ty.kind {
// Inside a `static mut`, `&mut [...]` is allowed.
ty::Array(..) | ty::Slice(_) if self.const_kind() == ConstKind::StaticMut => {
ty::Array(..) | ty::Slice(_)
if self.const_kind() == hir::ConstContext::Static(hir::Mutability::Mut) =>
{
true
}

View File

@ -182,7 +182,7 @@ pub fn run_passes(
}
fn mir_const_qualif(tcx: TyCtxt<'_>, def_id: DefId) -> ConstQualifs {
let const_kind = check_consts::ConstKind::for_item(tcx, def_id.expect_local());
let const_kind = tcx.hir().body_const_context(def_id.expect_local());
// No need to const-check a non-const `fn`.
if const_kind.is_none() {

View File

@ -13,6 +13,7 @@
//! move analysis runs after promotion on broken MIR.
use rustc_ast::ast::LitKind;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_middle::mir::traversal::ReversePostorder;
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
@ -30,7 +31,7 @@ use std::cell::Cell;
use std::{cmp, iter, mem};
use crate::const_eval::{is_const_fn, is_unstable_const_fn};
use crate::transform::check_consts::{is_lang_panic_fn, qualifs, ConstCx, ConstKind};
use crate::transform::check_consts::{is_lang_panic_fn, qualifs, ConstCx};
use crate::transform::{MirPass, MirSource};
/// A `MirPass` for promotion.
@ -352,7 +353,9 @@ impl<'tcx> Validator<'_, 'tcx> {
// In theory, any zero-sized value could be borrowed
// mutably without consequences. However, only &mut []
// is allowed right now, and only in functions.
if self.const_kind == Some(ConstKind::StaticMut) {
if self.const_kind
== Some(hir::ConstContext::Static(hir::Mutability::Mut))
{
// Inside a `static mut`, &mut [...] is also allowed.
match ty.kind {
ty::Array(..) | ty::Slice(_) => {}
@ -517,7 +520,7 @@ impl<'tcx> Validator<'_, 'tcx> {
if let Some(def_id) = c.check_static_ptr(self.tcx) {
// Only allow statics (not consts) to refer to other statics.
// FIXME(eddyb) does this matter at all for promotion?
let is_static = self.const_kind.map_or(false, |k| k.is_static());
let is_static = matches!(self.const_kind, Some(hir::ConstContext::Static(_)));
if !is_static {
return Err(Unpromotable);
}
@ -607,7 +610,7 @@ impl<'tcx> Validator<'_, 'tcx> {
// In theory, any zero-sized value could be borrowed
// mutably without consequences. However, only &mut []
// is allowed right now, and only in functions.
if self.const_kind == Some(ConstKind::StaticMut) {
if self.const_kind == Some(hir::ConstContext::Static(hir::Mutability::Mut)) {
// Inside a `static mut`, &mut [...] is also allowed.
match ty.kind {
ty::Array(..) | ty::Slice(_) => {}

View File

@ -1,326 +0,0 @@
//! Liveness analysis which computes liveness of MIR local variables at the boundary of basic
//! blocks.
//!
//! This analysis considers references as being used only at the point of the
//! borrow. This means that this does not track uses because of references that
//! already exist:
//!
//! ```rust
//! fn foo() {
//! x = 0;
//! // `x` is live here ...
//! GLOBAL = &x: *const u32;
//! // ... but not here, even while it can be accessed through `GLOBAL`.
//! foo();
//! x = 1;
//! // `x` is live again here, because it is assigned to `OTHER_GLOBAL`.
//! OTHER_GLOBAL = &x: *const u32;
//! // ...
//! }
//! ```
//!
//! This means that users of this analysis still have to check whether
//! pre-existing references can be used to access the value (e.g., at movable
//! generator yield points, all pre-existing references are invalidated, so this
//! doesn't matter).
use crate::transform::MirSource;
use crate::util::pretty::{dump_enabled, write_basic_block, write_mir_intro};
use rustc_data_structures::work_queue::WorkQueue;
use rustc_index::bit_set::BitSet;
use rustc_index::vec::{Idx, IndexVec};
use rustc_middle::mir::visit::{
MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext, Visitor,
};
use rustc_middle::mir::Local;
use rustc_middle::mir::*;
use rustc_middle::ty::{self, TyCtxt};
use std::fs;
use std::io::{self, BufWriter, Write};
use std::path::{Path, PathBuf};
pub type LiveVarSet = BitSet<Local>;
/// This gives the result of the liveness analysis at the boundary of
/// basic blocks.
///
/// The `V` type defines the set of variables that we computed
/// liveness for. This is often `Local`, in which case we computed
/// liveness for all variables -- but it can also be some other type,
/// which indicates a subset of the variables within the graph.
pub struct LivenessResult {
/// Live variables on exit to each basic block. This is equal to
/// the union of the `ins` for each successor.
pub outs: IndexVec<BasicBlock, LiveVarSet>,
}
/// Computes which local variables are live within the given function
/// `mir`, including drops.
pub fn liveness_of_locals(body: &Body<'_>) -> LivenessResult {
let num_live_vars = body.local_decls.len();
let def_use: IndexVec<_, DefsUses> =
body.basic_blocks().iter().map(|b| block(b, num_live_vars)).collect();
let mut outs: IndexVec<_, LiveVarSet> =
body.basic_blocks().indices().map(|_| LiveVarSet::new_empty(num_live_vars)).collect();
let mut bits = LiveVarSet::new_empty(num_live_vars);
// The dirty queue contains the set of basic blocks whose entry sets have changed since they
// were last processed. At the start of the analysis, we initialize the queue in post-order to
// make it more likely that the entry set for a given basic block will have the effects of all
// its successors in the CFG applied before it is processed.
//
// FIXME(ecstaticmorse): Reverse post-order on the reverse CFG may generate a better iteration
// order when cycles are present, but the overhead of computing the reverse CFG may outweigh
// any benefits. Benchmark this and find out.
let mut dirty_queue: WorkQueue<BasicBlock> = WorkQueue::with_none(body.basic_blocks().len());
for (bb, _) in traversal::postorder(&body) {
dirty_queue.insert(bb);
}
// Add blocks which are not reachable from START_BLOCK to the work queue. These blocks will
// be processed after the ones added above.
for bb in body.basic_blocks().indices() {
dirty_queue.insert(bb);
}
let predecessors = body.predecessors();
while let Some(bb) = dirty_queue.pop() {
// bits = use (bits - def)
bits.overwrite(&outs[bb]);
def_use[bb].apply(&mut bits);
// `bits` now contains the live variables on entry. Therefore,
// add `bits` to the `out` set for each predecessor; if those
// bits were not already present, then enqueue the predecessor
// as dirty.
//
// (note that `union` returns true if the `self` set changed)
for &pred_bb in &predecessors[bb] {
if outs[pred_bb].union(&bits) {
dirty_queue.insert(pred_bb);
}
}
}
LivenessResult { outs }
}
#[derive(Eq, PartialEq, Clone)]
pub enum DefUse {
Def,
Use,
Drop,
}
pub fn categorize(context: PlaceContext) -> Option<DefUse> {
match context {
///////////////////////////////////////////////////////////////////////////
// DEFS
PlaceContext::MutatingUse(MutatingUseContext::Store) |
// This is potentially both a def and a use...
PlaceContext::MutatingUse(MutatingUseContext::AsmOutput) |
// We let Call define the result in both the success and
// unwind cases. This is not really correct, however it
// does not seem to be observable due to the way that we
// generate MIR. To do things properly, we would apply
// the def in call only to the input from the success
// path and not the unwind path. -nmatsakis
PlaceContext::MutatingUse(MutatingUseContext::Call) |
PlaceContext::MutatingUse(MutatingUseContext::Yield) |
// Storage live and storage dead aren't proper defines, but we can ignore
// values that come before them.
PlaceContext::NonUse(NonUseContext::StorageLive) |
PlaceContext::NonUse(NonUseContext::StorageDead) => Some(DefUse::Def),
///////////////////////////////////////////////////////////////////////////
// REGULAR USES
//
// These are uses that occur *outside* of a drop. For the
// purposes of NLL, these are special in that **all** the
// lifetimes appearing in the variable must be live for each regular use.
PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) |
PlaceContext::MutatingUse(MutatingUseContext::Projection) |
// Borrows only consider their local used at the point of the borrow.
// This won't affect the results since we use this analysis for generators
// and we only care about the result at suspension points. Borrows cannot
// cross suspension points so this behavior is unproblematic.
PlaceContext::MutatingUse(MutatingUseContext::Borrow) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::UniqueBorrow) |
PlaceContext::MutatingUse(MutatingUseContext::AddressOf) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::AddressOf) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::Inspect) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) |
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) |
PlaceContext::NonUse(NonUseContext::AscribeUserTy) |
PlaceContext::MutatingUse(MutatingUseContext::Retag) =>
Some(DefUse::Use),
///////////////////////////////////////////////////////////////////////////
// DROP USES
//
// These are uses that occur in a DROP (a MIR drop, not a
// call to `std::mem::drop()`). For the purposes of NLL,
// uses in drop are special because `#[may_dangle]`
// attributes can affect whether lifetimes must be live.
PlaceContext::MutatingUse(MutatingUseContext::Drop) =>
Some(DefUse::Drop),
// Debug info is neither def nor use.
PlaceContext::NonUse(NonUseContext::VarDebugInfo) => None,
}
}
struct DefsUsesVisitor {
defs_uses: DefsUses,
}
#[derive(Eq, PartialEq, Clone)]
struct DefsUses {
defs: LiveVarSet,
uses: LiveVarSet,
}
impl DefsUses {
fn apply(&self, bits: &mut LiveVarSet) -> bool {
bits.subtract(&self.defs) | bits.union(&self.uses)
}
fn add_def(&mut self, index: Local) {
// If it was used already in the block, remove that use
// now that we found a definition.
//
// Example:
//
// // Defs = {X}, Uses = {}
// X = 5
// // Defs = {}, Uses = {X}
// use(X)
self.uses.remove(index);
self.defs.insert(index);
}
fn add_use(&mut self, index: Local) {
// Inverse of above.
//
// Example:
//
// // Defs = {}, Uses = {X}
// use(X)
// // Defs = {X}, Uses = {}
// X = 5
// // Defs = {}, Uses = {X}
// use(X)
self.defs.remove(index);
self.uses.insert(index);
}
}
impl<'tcx> Visitor<'tcx> for DefsUsesVisitor {
fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) {
match categorize(context) {
Some(DefUse::Def) => self.defs_uses.add_def(local),
Some(DefUse::Use | DefUse::Drop) => self.defs_uses.add_use(local),
_ => (),
}
}
}
fn block(b: &BasicBlockData<'_>, locals: usize) -> DefsUses {
let mut visitor = DefsUsesVisitor {
defs_uses: DefsUses {
defs: LiveVarSet::new_empty(locals),
uses: LiveVarSet::new_empty(locals),
},
};
let dummy_location = Location { block: BasicBlock::new(0), statement_index: 0 };
// Visit the various parts of the basic block in reverse. If we go
// forward, the logic in `add_def` and `add_use` would be wrong.
visitor.visit_terminator(b.terminator(), dummy_location);
for statement in b.statements.iter().rev() {
visitor.visit_statement(statement, dummy_location);
}
visitor.defs_uses
}
pub fn dump_mir<'tcx>(
tcx: TyCtxt<'tcx>,
pass_name: &str,
source: MirSource<'tcx>,
body: &Body<'tcx>,
result: &LivenessResult,
) {
if !dump_enabled(tcx, pass_name, source.def_id()) {
return;
}
let node_path = ty::print::with_forced_impl_filename_line(|| {
// see notes on #41697 below
tcx.def_path_str(source.def_id())
});
dump_matched_mir_node(tcx, pass_name, &node_path, source, body, result);
}
fn dump_matched_mir_node<'tcx>(
tcx: TyCtxt<'tcx>,
pass_name: &str,
node_path: &str,
source: MirSource<'tcx>,
body: &Body<'tcx>,
result: &LivenessResult,
) {
let mut file_path = PathBuf::new();
file_path.push(Path::new(&tcx.sess.opts.debugging_opts.dump_mir_dir));
let item_id = tcx.hir().as_local_hir_id(source.def_id().expect_local());
let file_name = format!("rustc.node{}{}-liveness.mir", item_id, pass_name);
file_path.push(&file_name);
let _ = fs::File::create(&file_path).and_then(|file| {
let mut file = BufWriter::new(file);
writeln!(file, "// MIR local liveness analysis for `{}`", node_path)?;
writeln!(file, "// source = {:?}", source)?;
writeln!(file, "// pass_name = {}", pass_name)?;
writeln!(file)?;
write_mir_fn(tcx, source, body, &mut file, result)?;
Ok(())
});
}
pub fn write_mir_fn<'tcx>(
tcx: TyCtxt<'tcx>,
src: MirSource<'tcx>,
body: &Body<'tcx>,
w: &mut dyn Write,
result: &LivenessResult,
) -> io::Result<()> {
write_mir_intro(tcx, src, body, w)?;
for block in body.basic_blocks().indices() {
let print = |w: &mut dyn Write, prefix, result: &IndexVec<BasicBlock, LiveVarSet>| {
let live: Vec<String> =
result[block].iter().map(|local| format!("{:?}", local)).collect();
writeln!(w, "{} {{{}}}", prefix, live.join(", "))
};
write_basic_block(tcx, block, body, &mut |_, _| Ok(()), w)?;
print(w, " ", &result.outs)?;
if block.index() + 1 != body.basic_blocks().len() {
writeln!(w)?;
}
}
writeln!(w, "}}")?;
Ok(())
}

View File

@ -8,7 +8,6 @@ pub mod storage;
mod alignment;
pub mod collect_writes;
mod graphviz;
pub mod liveness;
pub(crate) mod pretty;
pub use self::aggregate::expand_aggregate;

View File

@ -7,7 +7,6 @@
//! errors. We still look for those primitives in the MIR const-checker to ensure nothing slips
//! through, but errors for structured control flow in a `const` should be emitted here.
use rustc_ast::ast::Mutability;
use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
@ -19,8 +18,6 @@ use rustc_session::config::nightly_options;
use rustc_session::parse::feature_err;
use rustc_span::{sym, Span, Symbol};
use std::fmt;
/// An expression that is not *always* legal in a const context.
#[derive(Clone, Copy)]
enum NonConstExpr {
@ -65,46 +62,6 @@ impl NonConstExpr {
}
}
#[derive(Copy, Clone)]
enum ConstKind {
Static,
StaticMut,
ConstFn,
Const,
AnonConst,
}
impl ConstKind {
fn for_body(body: &hir::Body<'_>, tcx: TyCtxt<'_>) -> Option<Self> {
let owner = tcx.hir().body_owner(body.id());
let const_kind = match tcx.hir().body_owner_kind(owner) {
hir::BodyOwnerKind::Const => Self::Const,
hir::BodyOwnerKind::Static(Mutability::Mut) => Self::StaticMut,
hir::BodyOwnerKind::Static(Mutability::Not) => Self::Static,
hir::BodyOwnerKind::Fn if tcx.is_const_fn_raw(tcx.hir().local_def_id(owner)) => {
Self::ConstFn
}
hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => return None,
};
Some(const_kind)
}
}
impl fmt::Display for ConstKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Static => "static",
Self::StaticMut => "static mut",
Self::Const | Self::AnonConst => "const",
Self::ConstFn => "const fn",
};
write!(f, "{}", s)
}
}
fn check_mod_const_bodies(tcx: TyCtxt<'_>, module_def_id: DefId) {
let mut vis = CheckConstVisitor::new(tcx);
tcx.hir().visit_item_likes_in_module(module_def_id, &mut vis.as_deep_visitor());
@ -117,7 +74,7 @@ pub(crate) fn provide(providers: &mut Providers<'_>) {
#[derive(Copy, Clone)]
struct CheckConstVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
const_kind: Option<ConstKind>,
const_kind: Option<hir::ConstContext>,
}
impl<'tcx> CheckConstVisitor<'tcx> {
@ -147,7 +104,8 @@ impl<'tcx> CheckConstVisitor<'tcx> {
let const_kind = self
.const_kind
.expect("`const_check_violated` may only be called inside a const context");
let msg = format!("{} is not allowed in a `{}`", expr.name(), const_kind);
let msg = format!("{} is not allowed in a `{}`", expr.name(), const_kind.keyword_name());
let required_gates = required_gates.unwrap_or(&[]);
let missing_gates: Vec<_> =
@ -191,7 +149,7 @@ impl<'tcx> CheckConstVisitor<'tcx> {
}
/// Saves the parent `const_kind` before calling `f` and restores it afterwards.
fn recurse_into(&mut self, kind: Option<ConstKind>, f: impl FnOnce(&mut Self)) {
fn recurse_into(&mut self, kind: Option<hir::ConstContext>, f: impl FnOnce(&mut Self)) {
let parent_kind = self.const_kind;
self.const_kind = kind;
f(self);
@ -207,12 +165,13 @@ impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> {
}
fn visit_anon_const(&mut self, anon: &'tcx hir::AnonConst) {
let kind = Some(ConstKind::AnonConst);
let kind = Some(hir::ConstContext::Const);
self.recurse_into(kind, |this| intravisit::walk_anon_const(this, anon));
}
fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) {
let kind = ConstKind::for_body(body, self.tcx);
let owner = self.tcx.hir().body_owner_def_id(body.id());
let kind = self.tcx.hir().body_const_context(owner);
self.recurse_into(kind, |this| intravisit::walk_body(this, body));
}

View File

@ -0,0 +1,29 @@
// check-pass
#![allow(incomplete_features)]
#![feature(const_generics)]
struct Const<const N: usize>;
trait Foo<const N: usize> {}
impl<const N: usize> Foo<N> for Const<N> {}
fn foo_impl(_: impl Foo<3>) {}
fn foo_explicit<T: Foo<3>>(_: T) {}
fn foo_where<T>(_: T)
where
T: Foo<3>,
{
}
fn main() {
foo_impl(Const);
foo_impl(Const::<3>);
foo_explicit(Const);
foo_explicit(Const::<3>);
foo_where(Const);
foo_where(Const::<3>);
}

View File

@ -5,7 +5,7 @@ LL | const N: i32 = T::N << 42;
| ^^^^^^^^^^ attempt to shift left with overflow
|
note: the lint level is defined here
--> $DIR/lint-exceeding-bitshifts.rs:8:9
--> $DIR/lint-exceeding-bitshifts.rs:9:9
|
LL | #![warn(arithmetic_overflow, const_err)]
| ^^^^^^^^^^^^^^^^^^^

View File

@ -5,7 +5,7 @@ LL | const N: i32 = T::N << 42;
| ^^^^^^^^^^ attempt to shift left with overflow
|
note: the lint level is defined here
--> $DIR/lint-exceeding-bitshifts.rs:8:9
--> $DIR/lint-exceeding-bitshifts.rs:9:9
|
LL | #![warn(arithmetic_overflow, const_err)]
| ^^^^^^^^^^^^^^^^^^^

View File

@ -5,7 +5,7 @@ LL | const N: i32 = T::N << 42;
| ^^^^^^^^^^ attempt to shift left with overflow
|
note: the lint level is defined here
--> $DIR/lint-exceeding-bitshifts.rs:8:9
--> $DIR/lint-exceeding-bitshifts.rs:9:9
|
LL | #![warn(arithmetic_overflow, const_err)]
| ^^^^^^^^^^^^^^^^^^^

View File

@ -3,11 +3,11 @@
//[opt]compile-flags: -O
//[opt_with_overflow_checks]compile-flags: -C overflow-checks=on -O
// build-pass
// ignore-pass (test emits codegen-time warnings and verifies that they are not errors)
#![crate_type="lib"]
#![warn(arithmetic_overflow, const_err)]
#![allow(unused_variables)]
#![allow(dead_code)]
pub trait Foo {
const N: i32;