blob: bcee6d75b5a4a84974ee2dfdcc6d8c0dc94619bb [file] [log] [blame]
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A pass that qualifies constness of temporaries in constants,
//! static initializers and functions and also drives promotion.
//!
//! The Qualif flags below can be used to also provide better
//! diagnostics as to why a constant rvalue wasn't promoted.
use rustc_data_structures::bit_set::BitSet;
use rustc_data_structures::indexed_vec::IndexVec;
use rustc_data_structures::fx::FxHashSet;
use rustc::hir;
use rustc::hir::def_id::DefId;
use rustc::mir::interpret::ConstValue;
use rustc::traits::{self, TraitEngine};
use rustc::ty::{self, TyCtxt, Ty, TypeFoldable};
use rustc::ty::cast::CastTy;
use rustc::ty::query::Providers;
use rustc::mir::*;
use rustc::mir::traversal::ReversePostorder;
use rustc::mir::visit::{PlaceContext, Visitor, MutatingUseContext, NonMutatingUseContext};
use rustc::middle::lang_items;
use rustc_target::spec::abi::Abi;
use syntax::ast::LitKind;
use syntax::feature_gate::{UnstableFeatures, feature_err, emit_feature_err, GateIssue};
use syntax_pos::{Span, DUMMY_SP};
use std::fmt;
use rustc_data_structures::sync::Lrc;
use std::usize;
use transform::{MirPass, MirSource};
use super::promote_consts::{self, Candidate, TempState};
bitflags! {
// Borrows of temporaries can be promoted only if
// they have none of these qualifications, with
// the exception of `STATIC_REF` (in statics only).
struct Qualif: u8 {
// Constant containing interior mutability (UnsafeCell).
const MUTABLE_INTERIOR = 1 << 0;
// Constant containing an ADT that implements Drop.
const NEEDS_DROP = 1 << 1;
// Function argument.
const FN_ARGUMENT = 1 << 2;
// Not constant at all - non-`const fn` calls, asm!,
// pointer comparisons, ptr-to-int casts, etc.
const NOT_CONST = 1 << 3;
// Refers to temporaries which cannot be promoted as
// promote_consts decided they weren't simple enough.
const NOT_PROMOTABLE = 1 << 4;
// Const items can only have MUTABLE_INTERIOR
// and NOT_PROMOTABLE without producing an error.
const CONST_ERROR = !Qualif::MUTABLE_INTERIOR.bits &
!Qualif::NOT_PROMOTABLE.bits;
}
}
impl<'a, 'tcx> Qualif {
/// Remove flags which are impossible for the given type.
fn restrict(&mut self, ty: Ty<'tcx>,
tcx: TyCtxt<'a, 'tcx, 'tcx>,
param_env: ty::ParamEnv<'tcx>) {
if ty.is_freeze(tcx, param_env, DUMMY_SP) {
*self = *self - Qualif::MUTABLE_INTERIOR;
}
if !ty.needs_drop(tcx, param_env) {
*self = *self - Qualif::NEEDS_DROP;
}
}
}
/// What kind of item we are in.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum Mode {
Const,
Static,
StaticMut,
ConstFn,
Fn
}
impl fmt::Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Mode::Const => write!(f, "constant"),
Mode::Static | Mode::StaticMut => write!(f, "static"),
Mode::ConstFn => write!(f, "constant function"),
Mode::Fn => write!(f, "function")
}
}
}
struct Qualifier<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
mode: Mode,
span: Span,
def_id: DefId,
mir: &'a Mir<'tcx>,
rpo: ReversePostorder<'a, 'tcx>,
tcx: TyCtxt<'a, 'gcx, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
local_qualif: IndexVec<Local, Option<Qualif>>,
qualif: Qualif,
const_fn_arg_vars: BitSet<Local>,
temp_promotion_state: IndexVec<Local, TempState>,
promotion_candidates: Vec<Candidate>
}
impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>,
def_id: DefId,
mir: &'a Mir<'tcx>,
mode: Mode)
-> Qualifier<'a, 'tcx, 'tcx> {
assert!(def_id.is_local());
let mut rpo = traversal::reverse_postorder(mir);
let temps = promote_consts::collect_temps(mir, &mut rpo);
rpo.reset();
let param_env = tcx.param_env(def_id);
let mut local_qualif = IndexVec::from_elem(None, &mir.local_decls);
for arg in mir.args_iter() {
let mut qualif = Qualif::NEEDS_DROP;
qualif.restrict(mir.local_decls[arg].ty, tcx, param_env);
local_qualif[arg] = Some(qualif);
}
Qualifier {
mode,
span: mir.span,
def_id,
mir,
rpo,
tcx,
param_env,
local_qualif,
qualif: Qualif::empty(),
const_fn_arg_vars: BitSet::new_empty(mir.local_decls.len()),
temp_promotion_state: temps,
promotion_candidates: vec![]
}
}
// FIXME(eddyb) we could split the errors into meaningful
// categories, but enabling full miri would make that
// slightly pointless (even with feature-gating).
fn not_const(&mut self) {
self.add(Qualif::NOT_CONST);
if self.mode != Mode::Fn {
let mut err = struct_span_err!(
self.tcx.sess,
self.span,
E0019,
"{} contains unimplemented expression type",
self.mode
);
if self.tcx.sess.teach(&err.get_code().unwrap()) {
err.note("A function call isn't allowed in the const's initialization expression \
because the expression's value must be known at compile-time.");
err.note("Remember: you can't use a function call inside a const's initialization \
expression! However, you can use it anywhere else.");
}
err.emit();
}
}
/// Error about extra statements in a constant.
fn statement_like(&mut self) {
self.add(Qualif::NOT_CONST);
if self.mode != Mode::Fn {
let mut err = feature_err(
&self.tcx.sess.parse_sess,
"const_let",
self.span,
GateIssue::Language,
&format!("statements in {}s are unstable", self.mode),
);
if self.tcx.sess.teach(&err.get_code().unwrap()) {
err.note("Blocks in constants may only contain items (such as constant, function \
definition, etc...) and a tail expression.");
err.help("To avoid it, you have to replace the non-item object.");
}
err.emit();
}
}
/// Add the given qualification to self.qualif.
fn add(&mut self, qualif: Qualif) {
self.qualif = self.qualif | qualif;
}
/// Add the given type's qualification to self.qualif.
fn add_type(&mut self, ty: Ty<'tcx>) {
self.add(Qualif::MUTABLE_INTERIOR | Qualif::NEEDS_DROP);
self.qualif.restrict(ty, self.tcx, self.param_env);
}
/// Within the provided closure, self.qualif will start
/// out empty, and its value after the closure returns will
/// be combined with the value before the call to nest.
fn nest<F: FnOnce(&mut Self)>(&mut self, f: F) {
let original = self.qualif;
self.qualif = Qualif::empty();
f(self);
self.add(original);
}
/// Assign the current qualification to the given destination.
fn assign(&mut self, dest: &Place<'tcx>, location: Location) {
trace!("assign: {:?}", dest);
let qualif = self.qualif;
let span = self.span;
let store = |slot: &mut Option<Qualif>| {
if slot.is_some() {
span_bug!(span, "multiple assignments to {:?}", dest);
}
*slot = Some(qualif);
};
// Only handle promotable temps in non-const functions.
if self.mode == Mode::Fn {
if let Place::Local(index) = *dest {
if self.mir.local_kind(index) == LocalKind::Temp
&& self.temp_promotion_state[index].is_promotable() {
debug!("store to promotable temp {:?} ({:?})", index, qualif);
store(&mut self.local_qualif[index]);
}
}
return;
}
if self.tcx.features().const_let {
let mut dest = dest;
let index = loop {
match dest {
// with `const_let` active, we treat all locals equal
Place::Local(index) => break *index,
// projections are transparent for assignments
// we qualify the entire destination at once, even if just a field would have
// stricter qualification
Place::Projection(proj) => {
// Catch more errors in the destination. `visit_place` also checks various
// projection rules like union field access and raw pointer deref
self.visit_place(
dest,
PlaceContext::MutatingUse(MutatingUseContext::Store),
location
);
dest = &proj.base;
},
Place::Promoted(..) => bug!("promoteds don't exist yet during promotion"),
Place::Static(..) => {
// Catch more errors in the destination. `visit_place` also checks that we
// do not try to access statics from constants or try to mutate statics
self.visit_place(
dest,
PlaceContext::MutatingUse(MutatingUseContext::Store),
location
);
return;
}
}
};
debug!("store to var {:?}", index);
match &mut self.local_qualif[index] {
// this is overly restrictive, because even full assignments do not clear the qualif
// While we could special case full assignments, this would be inconsistent with
// aggregates where we overwrite all fields via assignments, which would not get
// that feature.
Some(ref mut qualif) => *qualif = *qualif | self.qualif,
// insert new qualification
qualif @ None => *qualif = Some(self.qualif),
}
return;
}
match *dest {
Place::Local(index) if self.mir.local_kind(index) == LocalKind::Temp ||
self.mir.local_kind(index) == LocalKind::ReturnPointer => {
debug!("store to {:?} (temp or return pointer)", index);
store(&mut self.local_qualif[index])
}
Place::Projection(box Projection {
base: Place::Local(index),
elem: ProjectionElem::Deref
}) if self.mir.local_kind(index) == LocalKind::Temp
&& self.mir.local_decls[index].ty.is_box()
&& self.local_qualif[index].map_or(false, |qualif| {
qualif.contains(Qualif::NOT_CONST)
}) => {
// Part of `box expr`, we should've errored
// already for the Box allocation Rvalue.
}
// This must be an explicit assignment.
_ => {
// Catch more errors in the destination.
self.visit_place(
dest,
PlaceContext::MutatingUse(MutatingUseContext::Store),
location
);
self.statement_like();
}
}
}
/// Qualify a whole const, static initializer or const fn.
fn qualify_const(&mut self) -> (Qualif, Lrc<BitSet<Local>>) {
debug!("qualifying {} {:?}", self.mode, self.def_id);
let mir = self.mir;
let mut seen_blocks = BitSet::new_empty(mir.basic_blocks().len());
let mut bb = START_BLOCK;
loop {
seen_blocks.insert(bb.index());
self.visit_basic_block_data(bb, &mir[bb]);
let target = match mir[bb].terminator().kind {
TerminatorKind::Goto { target } |
TerminatorKind::Drop { target, .. } |
TerminatorKind::Assert { target, .. } |
TerminatorKind::Call { destination: Some((_, target)), .. } => {
Some(target)
}
// Non-terminating calls cannot produce any value.
TerminatorKind::Call { destination: None, .. } => {
break;
}
TerminatorKind::SwitchInt {..} |
TerminatorKind::DropAndReplace { .. } |
TerminatorKind::Resume |
TerminatorKind::Abort |
TerminatorKind::GeneratorDrop |
TerminatorKind::Yield { .. } |
TerminatorKind::Unreachable |
TerminatorKind::FalseEdges { .. } |
TerminatorKind::FalseUnwind { .. } => None,
TerminatorKind::Return => {
if !self.tcx.features().const_let {
// Check for unused values. This usually means
// there are extra statements in the AST.
for temp in mir.temps_iter() {
if self.local_qualif[temp].is_none() {
continue;
}
let state = self.temp_promotion_state[temp];
if let TempState::Defined { location, uses: 0 } = state {
let data = &mir[location.block];
let stmt_idx = location.statement_index;
// Get the span for the initialization.
let source_info = if stmt_idx < data.statements.len() {
data.statements[stmt_idx].source_info
} else {
data.terminator().source_info
};
self.span = source_info.span;
// Treat this as a statement in the AST.
self.statement_like();
}
}
// Make sure there are no extra unassigned variables.
self.qualif = Qualif::NOT_CONST;
for index in mir.vars_iter() {
if !self.const_fn_arg_vars.contains(index) {
debug!("unassigned variable {:?}", index);
self.assign(&Place::Local(index), Location {
block: bb,
statement_index: usize::MAX,
});
}
}
}
break;
}
};
match target {
// No loops allowed.
Some(target) if !seen_blocks.contains(target.index()) => {
bb = target;
}
_ => {
self.not_const();
break;
}
}
}
self.qualif = self.local_qualif[RETURN_PLACE].unwrap_or(Qualif::NOT_CONST);
// Account for errors in consts by using the
// conservative type qualification instead.
if self.qualif.intersects(Qualif::CONST_ERROR) {
self.qualif = Qualif::empty();
let return_ty = mir.return_ty();
self.add_type(return_ty);
}
// 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 {
Candidate::Ref(Location { block: bb, statement_index: stmt_idx }) => {
match self.mir[bb].statements[stmt_idx].kind {
StatementKind::Assign(_, box Rvalue::Ref(_, _, Place::Local(index))) => {
promoted_temps.insert(index);
}
_ => {}
}
}
Candidate::Argument { .. } => {}
}
}
(self.qualif, Lrc::new(promoted_temps))
}
fn is_const_panic_fn(&self, def_id: DefId) -> bool {
Some(def_id) == self.tcx.lang_items().panic_fn() ||
Some(def_id) == self.tcx.lang_items().begin_panic_fn()
}
}
/// Accumulates an Rvalue or Call's effects in self.qualif.
/// For functions (constant or not), it also records
/// candidates for promotion in promotion_candidates.
impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
fn visit_local(&mut self,
&local: &Local,
_: PlaceContext<'tcx>,
_: Location) {
debug!("visit_local: local={:?}", local);
let kind = self.mir.local_kind(local);
match kind {
LocalKind::ReturnPointer => {
self.not_const();
}
LocalKind::Var if !self.tcx.features().const_let => {
if self.mode != Mode::Fn {
emit_feature_err(&self.tcx.sess.parse_sess, "const_let",
self.span, GateIssue::Language,
&format!("let bindings in {}s are unstable",self.mode));
}
self.add(Qualif::NOT_CONST);
}
LocalKind::Var |
LocalKind::Arg |
LocalKind::Temp => {
if let LocalKind::Arg = kind {
self.add(Qualif::FN_ARGUMENT);
}
if !self.temp_promotion_state[local].is_promotable() {
debug!("visit_local: (not promotable) local={:?}", local);
self.add(Qualif::NOT_PROMOTABLE);
}
if let Some(qualif) = self.local_qualif[local] {
self.add(qualif);
} else {
self.not_const();
}
}
}
}
fn visit_place(&mut self,
place: &Place<'tcx>,
context: PlaceContext<'tcx>,
location: Location) {
debug!("visit_place: place={:?} context={:?} location={:?}", place, context, location);
match *place {
Place::Local(ref local) => self.visit_local(local, context, location),
Place::Promoted(_) => bug!("promoting already promoted MIR"),
Place::Static(ref global) => {
if self.tcx
.get_attrs(global.def_id)
.iter()
.any(|attr| attr.check_name("thread_local")) {
if self.mode != Mode::Fn {
span_err!(self.tcx.sess, self.span, E0625,
"thread-local statics cannot be \
accessed at compile-time");
}
self.add(Qualif::NOT_CONST);
return;
}
// Only allow statics (not consts) to refer to other statics.
if self.mode == Mode::Static || self.mode == Mode::StaticMut {
if context.is_mutating_use() {
// this is not strictly necessary as miri will also bail out
// For interior mutability we can't really catch this statically as that
// goes through raw pointers and intermediate temporaries, so miri has
// to catch this anyway
self.tcx.sess.span_err(
self.span,
"cannot mutate statics in the initializer of another static",
);
}
return;
}
self.add(Qualif::NOT_CONST);
if self.mode != Mode::Fn {
let mut err = struct_span_err!(self.tcx.sess, self.span, E0013,
"{}s cannot refer to statics, use \
a constant instead", self.mode);
if self.tcx.sess.teach(&err.get_code().unwrap()) {
err.note(
"Static and const variables can refer to other const variables. But a \
const variable cannot refer to a static variable."
);
err.help(
"To fix this, the value can be extracted as a const and then used."
);
}
err.emit()
}
}
Place::Projection(ref proj) => {
self.nest(|this| {
this.super_place(place, context, location);
match proj.elem {
ProjectionElem::Deref => {
this.add(Qualif::NOT_CONST);
let base_ty = proj.base.ty(this.mir, this.tcx).to_ty(this.tcx);
match this.mode {
Mode::Fn => {},
_ => {
if let ty::RawPtr(_) = base_ty.sty {
if !this.tcx.features().const_raw_ptr_deref {
emit_feature_err(
&this.tcx.sess.parse_sess, "const_raw_ptr_deref",
this.span, GateIssue::Language,
&format!(
"dereferencing raw pointers in {}s is unstable",
this.mode,
),
);
}
}
}
}
}
ProjectionElem::Field(..) |
ProjectionElem::Index(_) => {
let base_ty = proj.base.ty(this.mir, this.tcx).to_ty(this.tcx);
if let Some(def) = base_ty.ty_adt_def() {
if def.is_union() {
match this.mode {
Mode::Fn => this.not_const(),
Mode::ConstFn => {
if !this.tcx.features().const_fn_union {
emit_feature_err(
&this.tcx.sess.parse_sess, "const_fn_union",
this.span, GateIssue::Language,
"unions in const fn are unstable",
);
}
},
| Mode::Static
| Mode::StaticMut
| Mode::Const
=> {},
}
}
}
let ty = place.ty(this.mir, this.tcx).to_ty(this.tcx);
this.qualif.restrict(ty, this.tcx, this.param_env);
}
ProjectionElem::ConstantIndex {..} |
ProjectionElem::Subslice {..} |
ProjectionElem::Downcast(..) => {
this.not_const()
}
}
});
}
}
}
fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
debug!("visit_operand: operand={:?} location={:?}", operand, location);
self.super_operand(operand, location);
match *operand {
Operand::Copy(_) |
Operand::Move(_) => {
// Mark the consumed locals to indicate later drops are noops.
if let Operand::Move(Place::Local(local)) = *operand {
self.local_qualif[local] = self.local_qualif[local].map(|q|
q - Qualif::NEEDS_DROP
);
}
}
Operand::Constant(ref constant) => {
if let ConstValue::Unevaluated(def_id, _) = constant.literal.val {
// Don't peek inside trait associated constants.
if self.tcx.trait_of_item(def_id).is_some() {
self.add_type(constant.literal.ty);
} else {
let (bits, _) = self.tcx.at(constant.span).mir_const_qualif(def_id);
let qualif = Qualif::from_bits(bits).expect("invalid mir_const_qualif");
self.add(qualif);
// Just in case the type is more specific than
// the definition, e.g. impl associated const
// with type parameters, take it into account.
self.qualif.restrict(constant.literal.ty, self.tcx, self.param_env);
}
}
}
}
}
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
debug!("visit_rvalue: rvalue={:?} location={:?}", rvalue, location);
// Recurse through operands and places.
if let Rvalue::Ref(region, kind, ref place) = *rvalue {
let mut is_reborrow = false;
if let Place::Projection(ref proj) = *place {
if let ProjectionElem::Deref = proj.elem {
let base_ty = proj.base.ty(self.mir, self.tcx).to_ty(self.tcx);
if let ty::Ref(..) = base_ty.sty {
is_reborrow = true;
}
}
}
if is_reborrow {
let ctx = match kind {
BorrowKind::Shared =>
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow(region)),
BorrowKind::Shallow =>
PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow(region)),
BorrowKind::Unique =>
PlaceContext::NonMutatingUse(NonMutatingUseContext::UniqueBorrow(region)),
BorrowKind::Mut { .. } =>
PlaceContext::MutatingUse(MutatingUseContext::Borrow(region)),
};
self.super_place(place, ctx, location);
} else {
self.super_rvalue(rvalue, location);
}
} else {
self.super_rvalue(rvalue, location);
}
match *rvalue {
Rvalue::Use(_) |
Rvalue::Repeat(..) |
Rvalue::UnaryOp(UnOp::Neg, _) |
Rvalue::UnaryOp(UnOp::Not, _) |
Rvalue::NullaryOp(NullOp::SizeOf, _) |
Rvalue::CheckedBinaryOp(..) |
Rvalue::Cast(CastKind::ReifyFnPointer, ..) |
Rvalue::Cast(CastKind::UnsafeFnPointer, ..) |
Rvalue::Cast(CastKind::ClosureFnPointer, ..) |
Rvalue::Cast(CastKind::Unsize, ..) |
Rvalue::Discriminant(..) |
Rvalue::Len(_) => {}
Rvalue::Ref(_, kind, ref place) => {
let ty = place.ty(self.mir, self.tcx).to_ty(self.tcx);
// Default to forbidding the borrow and/or its promotion,
// due to the potential for direct or interior mutability,
// and only proceed by setting `forbidden_mut` to `false`.
let mut forbidden_mut = true;
if let BorrowKind::Mut { .. } = kind {
// 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.mode == Mode::StaticMut {
// Inside a `static mut`, &mut [...] is also allowed.
match ty.sty {
ty::Array(..) | ty::Slice(_) => forbidden_mut = false,
_ => {}
}
} else if let ty::Array(_, len) = ty.sty {
// FIXME(eddyb) the `self.mode == Mode::Fn` condition
// seems unnecessary, given that this is merely a ZST.
if len.unwrap_usize(self.tcx) == 0 && self.mode == Mode::Fn {
forbidden_mut = false;
}
}
if forbidden_mut {
self.add(Qualif::NOT_CONST);
if self.mode != Mode::Fn {
let mut err = struct_span_err!(self.tcx.sess, self.span, E0017,
"references in {}s may only refer \
to immutable values", self.mode);
err.span_label(self.span, format!("{}s require immutable values",
self.mode));
if self.tcx.sess.teach(&err.get_code().unwrap()) {
err.note("References in statics and constants may only refer to \
immutable values.\n\n\
Statics are shared everywhere, and if they refer to \
mutable data one might violate memory safety since \
holding multiple mutable references to shared data is \
not allowed.\n\n\
If you really want global mutable state, try using \
static mut or a global UnsafeCell.");
}
err.emit();
}
}
} else {
// Constants cannot be borrowed if they contain interior mutability as
// it means that our "silent insertion of statics" could change
// initializer values (very bad).
if self.qualif.contains(Qualif::MUTABLE_INTERIOR) {
// A reference of a MUTABLE_INTERIOR place is instead
// NOT_CONST (see `if forbidden_mut` below), to avoid
// duplicate errors (from reborrowing, for example).
self.qualif = self.qualif - Qualif::MUTABLE_INTERIOR;
if self.mode != Mode::Fn {
span_err!(self.tcx.sess, self.span, E0492,
"cannot borrow a constant which may contain \
interior mutability, create a static instead");
}
} else {
// We allow immutable borrows of frozen data.
forbidden_mut = false;
}
}
debug!("visit_rvalue: forbidden_mut={:?}", forbidden_mut);
if forbidden_mut {
self.add(Qualif::NOT_CONST);
} else {
// We might have a candidate for promotion.
let candidate = Candidate::Ref(location);
// We can only promote interior borrows of promotable temps.
let mut place = place;
while let Place::Projection(ref proj) = *place {
if proj.elem == ProjectionElem::Deref {
break;
}
place = &proj.base;
}
debug!("visit_rvalue: place={:?}", place);
if let Place::Local(local) = *place {
if self.mir.local_kind(local) == LocalKind::Temp {
debug!("visit_rvalue: local={:?}", local);
if let Some(qualif) = self.local_qualif[local] {
// `forbidden_mut` is false, so we can safely ignore
// `MUTABLE_INTERIOR` from the local's qualifications.
// This allows borrowing fields which don't have
// `MUTABLE_INTERIOR`, from a type that does, e.g.:
// `let _: &'static _ = &(Cell::new(1), 2).1;`
debug!("visit_rvalue: qualif={:?}", qualif);
if (qualif - Qualif::MUTABLE_INTERIOR).is_empty() {
debug!("visit_rvalue: candidate={:?}", candidate);
self.promotion_candidates.push(candidate);
}
}
}
}
}
}
Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) => {
let operand_ty = operand.ty(self.mir, self.tcx);
let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast");
let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
match (cast_in, cast_out) {
(CastTy::Ptr(_), CastTy::Int(_)) |
(CastTy::FnPtr, CastTy::Int(_)) => {
if let Mode::Fn = self.mode {
// in normal functions, mark such casts as not promotable
self.add(Qualif::NOT_CONST);
} else if !self.tcx.features().const_raw_ptr_to_usize_cast {
// in const fn and constants require the feature gate
// FIXME: make it unsafe inside const fn and constants
emit_feature_err(
&self.tcx.sess.parse_sess, "const_raw_ptr_to_usize_cast",
self.span, GateIssue::Language,
&format!(
"casting pointers to integers in {}s is unstable",
self.mode,
),
);
}
}
_ => {}
}
}
Rvalue::BinaryOp(op, ref lhs, _) => {
if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.mir, self.tcx).sty {
assert!(op == BinOp::Eq || op == BinOp::Ne ||
op == BinOp::Le || op == BinOp::Lt ||
op == BinOp::Ge || op == BinOp::Gt ||
op == BinOp::Offset);
if let Mode::Fn = self.mode {
// raw pointer operations are not allowed inside promoteds
self.add(Qualif::NOT_CONST);
} else if !self.tcx.features().const_compare_raw_pointers {
// require the feature gate inside constants and const fn
// FIXME: make it unsafe to use these operations
emit_feature_err(
&self.tcx.sess.parse_sess,
"const_compare_raw_pointers",
self.span,
GateIssue::Language,
&format!("comparing raw pointers inside {}", self.mode),
);
}
}
}
Rvalue::NullaryOp(NullOp::Box, _) => {
self.add(Qualif::NOT_CONST);
if self.mode != Mode::Fn {
let mut err = struct_span_err!(self.tcx.sess, self.span, E0010,
"allocations are not allowed in {}s", self.mode);
err.span_label(self.span, format!("allocation not allowed in {}s", self.mode));
if self.tcx.sess.teach(&err.get_code().unwrap()) {
err.note(
"The value of statics and constants must be known at compile time, \
and they live for the entire lifetime of a program. Creating a boxed \
value allocates memory on the heap at runtime, and therefore cannot \
be done at compile time."
);
}
err.emit();
}
}
Rvalue::Aggregate(ref kind, _) => {
if let AggregateKind::Adt(def, ..) = **kind {
if def.has_dtor(self.tcx) {
self.add(Qualif::NEEDS_DROP);
}
if Some(def.did) == self.tcx.lang_items().unsafe_cell_type() {
let ty = rvalue.ty(self.mir, self.tcx);
self.add_type(ty);
assert!(self.qualif.contains(Qualif::MUTABLE_INTERIOR));
}
}
}
}
}
fn visit_terminator_kind(&mut self,
bb: BasicBlock,
kind: &TerminatorKind<'tcx>,
location: Location) {
debug!("visit_terminator_kind: bb={:?} kind={:?} location={:?}", bb, kind, location);
if let TerminatorKind::Call { ref func, ref args, ref destination, .. } = *kind {
self.visit_operand(func, location);
let fn_ty = func.ty(self.mir, self.tcx);
let mut callee_def_id = None;
let mut is_shuffle = false;
let mut is_const_fn = false;
let mut is_promotable_const_fn = false;
match fn_ty.sty {
ty::FnDef(def_id, _) => {
callee_def_id = Some(def_id);
match self.tcx.fn_sig(def_id).abi() {
Abi::RustIntrinsic |
Abi::PlatformIntrinsic => {
assert!(!self.tcx.is_const_fn(def_id));
match &self.tcx.item_name(def_id).as_str()[..] {
| "size_of"
| "min_align_of"
| "needs_drop"
| "type_id"
| "bswap"
| "bitreverse"
| "ctpop"
| "cttz"
| "cttz_nonzero"
| "ctlz"
| "ctlz_nonzero"
| "overflowing_add"
| "overflowing_sub"
| "overflowing_mul"
| "unchecked_shl"
| "unchecked_shr"
| "rotate_left"
| "rotate_right"
| "add_with_overflow"
| "sub_with_overflow"
| "mul_with_overflow"
// no need to check feature gates, intrinsics are only callable
// from the libstd or with forever unstable feature gates
=> is_const_fn = true,
// special intrinsic that can be called diretly without an intrinsic
// feature gate needs a language feature gate
"transmute" => {
// never promote transmute calls
if self.mode != Mode::Fn {
is_const_fn = true;
// const eval transmute calls only with the feature gate
if !self.tcx.features().const_transmute {
emit_feature_err(
&self.tcx.sess.parse_sess, "const_transmute",
self.span, GateIssue::Language,
&format!("The use of std::mem::transmute() \
is gated in {}s", self.mode));
}
}
}
name if name.starts_with("simd_shuffle") => {
is_shuffle = true;
}
_ => {}
}
}
_ => {
// in normal functions we only care about promotion
if self.mode == Mode::Fn {
// never promote const fn calls of
// functions without #[rustc_promotable]
if self.tcx.is_promotable_const_fn(def_id) {
is_const_fn = true;
is_promotable_const_fn = true;
} else if self.tcx.is_const_fn(def_id) {
is_const_fn = true;
}
} else {
// stable const fn or unstable const fns with their feature gate
// active
if self.tcx.is_const_fn(def_id) {
is_const_fn = true;
} else if self.is_const_panic_fn(def_id) {
// check the const_panic feature gate
// FIXME: cannot allow this inside `allow_internal_unstable`
// because that would make `panic!` insta stable in constants,
// since the macro is marked with the attr
if self.tcx.features().const_panic {
is_const_fn = true;
} else {
// don't allow panics in constants without the feature gate
emit_feature_err(
&self.tcx.sess.parse_sess,
"const_panic",
self.span,
GateIssue::Language,
&format!("panicking in {}s is unstable", self.mode),
);
}
} else if let Some(feat) = self.tcx.is_unstable_const_fn(def_id) {
// check `#[unstable]` const fns or `#[rustc_const_unstable]`
// functions without the feature gate active in this crate to
// report a better error message than the one below
if self.span.allows_unstable() {
// `allow_internal_unstable` can make such calls stable
is_const_fn = true;
} else {
let mut err = self.tcx.sess.struct_span_err(self.span,
&format!("`{}` is not yet stable as a const fn",
self.tcx.item_path_str(def_id)));
help!(&mut err,
"in Nightly builds, add `#![feature({})]` \
to the crate attributes to enable",
feat);
err.emit();
}
} else {
// FIXME(#24111) Remove this check when const fn stabilizes
let (msg, note) = if let UnstableFeatures::Disallow =
self.tcx.sess.opts.unstable_features {
(format!("calls in {}s are limited to \
tuple structs and tuple variants",
self.mode),
Some("a limited form of compile-time function \
evaluation is available on a nightly \
compiler via `const fn`"))
} else {
(format!("calls in {}s are limited \
to constant functions, \
tuple structs and tuple variants",
self.mode),
None)
};
let mut err = struct_span_err!(
self.tcx.sess,
self.span,
E0015,
"{}",
msg,
);
if let Some(note) = note {
err.span_note(self.span, note);
}
err.emit();
}
}
}
}
},
ty::FnPtr(_) => {
if self.mode != Mode::Fn {
let mut err = self.tcx.sess.struct_span_err(
self.span,
&format!("function pointers are not allowed in const fn"));
err.emit();
}
},
_ => {
self.not_const();
return
}
}
let constant_arguments = callee_def_id.and_then(|id| {
args_required_const(self.tcx, id)
});
for (i, arg) in args.iter().enumerate() {
self.nest(|this| {
this.visit_operand(arg, location);
if this.mode != Mode::Fn {
return
}
let candidate = Candidate::Argument { bb, index: i };
if is_shuffle && i == 2 {
if this.qualif.is_empty() {
debug!("visit_terminator_kind: candidate={:?}", candidate);
this.promotion_candidates.push(candidate);
} else {
span_err!(this.tcx.sess, this.span, E0526,
"shuffle indices are not constant");
}
return
}
let constant_arguments = match constant_arguments.as_ref() {
Some(s) => s,
None => return,
};
if !constant_arguments.contains(&i) {
return
}
// Since the argument is required to be constant,
// we care about constness, not promotability.
// If we checked for promotability, we'd miss out on
// the results of function calls (which are never promoted
// in runtime code)
// 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.
if (this.qualif - Qualif::NOT_PROMOTABLE).is_empty() {
debug!("visit_terminator_kind: candidate={:?}", candidate);
this.promotion_candidates.push(candidate);
} else {
this.tcx.sess.span_err(this.span,
&format!("argument {} is required to be a constant",
i + 1));
}
});
}
// non-const fn calls.
if !is_const_fn {
self.qualif = Qualif::NOT_CONST;
if self.mode != Mode::Fn {
self.tcx.sess.delay_span_bug(
self.span,
"should have reported an error about non-const fn calls in constants",
)
}
}
if let Some((ref dest, _)) = *destination {
// Avoid propagating irrelevant callee/argument qualifications.
if self.qualif.intersects(Qualif::CONST_ERROR) {
self.qualif = Qualif::NOT_CONST;
} else {
// Be conservative about the returned value of a const fn.
let tcx = self.tcx;
let ty = dest.ty(self.mir, tcx).to_ty(tcx);
if is_const_fn && !is_promotable_const_fn && self.mode == Mode::Fn {
self.qualif = Qualif::NOT_PROMOTABLE;
} else {
self.qualif = Qualif::empty();
}
self.add_type(ty);
}
self.assign(dest, location);
}
} else if let TerminatorKind::Drop { location: ref place, .. } = *kind {
self.super_terminator_kind(bb, kind, location);
// Deny *any* live drops anywhere other than functions.
if self.mode != Mode::Fn {
// HACK(eddyb) Emulate a bit of dataflow analysis,
// conservatively, that drop elaboration will do.
let needs_drop = if let Place::Local(local) = *place {
if self.local_qualif[local].map_or(true, |q| q.contains(Qualif::NEEDS_DROP)) {
Some(self.mir.local_decls[local].source_info.span)
} else {
None
}
} else {
Some(self.span)
};
if let Some(span) = needs_drop {
// Double-check the type being dropped, to minimize false positives.
let ty = place.ty(self.mir, self.tcx).to_ty(self.tcx);
if ty.needs_drop(self.tcx, self.param_env) {
struct_span_err!(self.tcx.sess, span, E0493,
"destructors cannot be evaluated at compile-time")
.span_label(span, format!("{}s cannot evaluate destructors",
self.mode))
.emit();
}
}
}
} else {
// Qualify any operands inside other terminators.
self.super_terminator_kind(bb, kind, location);
}
}
fn visit_assign(&mut self,
_: BasicBlock,
dest: &Place<'tcx>,
rvalue: &Rvalue<'tcx>,
location: Location) {
debug!("visit_assign: dest={:?} rvalue={:?} location={:?}", dest, rvalue, location);
self.visit_rvalue(rvalue, location);
// Check the allowed const fn argument forms.
if let (Mode::ConstFn, &Place::Local(index)) = (self.mode, dest) {
if self.mir.local_kind(index) == LocalKind::Var &&
self.const_fn_arg_vars.insert(index) &&
!self.tcx.features().const_let {
// Direct use of an argument is permitted.
match *rvalue {
Rvalue::Use(Operand::Copy(Place::Local(local))) |
Rvalue::Use(Operand::Move(Place::Local(local))) => {
if self.mir.local_kind(local) == LocalKind::Arg {
return;
}
}
_ => {}
}
// Avoid a generic error for other uses of arguments.
if self.qualif.contains(Qualif::FN_ARGUMENT) {
let decl = &self.mir.local_decls[index];
let mut err = feature_err(
&self.tcx.sess.parse_sess,
"const_let",
decl.source_info.span,
GateIssue::Language,
"arguments of constant functions can only be immutable by-value bindings"
);
if self.tcx.sess.teach(&err.get_code().unwrap()) {
err.note("Constant functions are not allowed to mutate anything. Thus, \
binding to an argument with a mutable pattern is not allowed.");
err.note("Remove any mutable bindings from the argument list to fix this \
error. In case you need to mutate the argument, try lazily \
initializing a global variable instead of using a const fn, or \
refactoring the code to a functional style to avoid mutation if \
possible.");
}
err.emit();
return;
}
}
}
self.assign(dest, location);
}
fn visit_source_info(&mut self, source_info: &SourceInfo) {
debug!("visit_source_info: source_info={:?}", source_info);
self.span = source_info.span;
}
fn visit_statement(&mut self, bb: BasicBlock, statement: &Statement<'tcx>, location: Location) {
debug!("visit_statement: bb={:?} statement={:?} location={:?}", bb, statement, location);
self.nest(|this| {
this.visit_source_info(&statement.source_info);
match statement.kind {
StatementKind::Assign(ref place, ref rvalue) => {
this.visit_assign(bb, place, rvalue, location);
}
StatementKind::FakeRead(..) |
StatementKind::SetDiscriminant { .. } |
StatementKind::StorageLive(_) |
StatementKind::StorageDead(_) |
StatementKind::InlineAsm {..} |
StatementKind::Retag { .. } |
StatementKind::EscapeToRaw { .. } |
StatementKind::AscribeUserType(..) |
StatementKind::Nop => {}
}
});
}
fn visit_terminator(&mut self,
bb: BasicBlock,
terminator: &Terminator<'tcx>,
location: Location) {
debug!("visit_terminator: bb={:?} terminator={:?} location={:?}", bb, terminator, location);
self.nest(|this| this.super_terminator(bb, terminator, location));
}
}
pub fn provide(providers: &mut Providers) {
*providers = Providers {
mir_const_qualif,
..*providers
};
}
fn mir_const_qualif<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
def_id: DefId)
-> (u8, Lrc<BitSet<Local>>) {
// NB: This `borrow()` is guaranteed to be valid (i.e., the value
// cannot yet be stolen), because `mir_validated()`, which steals
// from `mir_const(), forces this query to execute before
// performing the steal.
let mir = &tcx.mir_const(def_id).borrow();
if mir.return_ty().references_error() {
tcx.sess.delay_span_bug(mir.span, "mir_const_qualif: Mir had errors");
return (Qualif::NOT_CONST.bits(), Lrc::new(BitSet::new_empty(0)));
}
let mut qualifier = Qualifier::new(tcx, def_id, mir, Mode::Const);
let (qualif, promoted_temps) = qualifier.qualify_const();
(qualif.bits(), promoted_temps)
}
pub struct QualifyAndPromoteConstants;
impl MirPass for QualifyAndPromoteConstants {
fn run_pass<'a, 'tcx>(&self,
tcx: TyCtxt<'a, 'tcx, 'tcx>,
src: MirSource,
mir: &mut Mir<'tcx>) {
// There's not really any point in promoting errorful MIR.
if mir.return_ty().references_error() {
tcx.sess.delay_span_bug(mir.span, "QualifyAndPromoteConstants: Mir had errors");
return;
}
if src.promoted.is_some() {
return;
}
let def_id = src.def_id;
let id = tcx.hir.as_local_node_id(def_id).unwrap();
let mut const_promoted_temps = None;
let mode = match tcx.hir.body_owner_kind(id) {
hir::BodyOwnerKind::Fn => {
if tcx.is_const_fn(def_id) {
Mode::ConstFn
} else {
Mode::Fn
}
}
hir::BodyOwnerKind::Const => {
const_promoted_temps = Some(tcx.mir_const_qualif(def_id).1);
Mode::Const
}
hir::BodyOwnerKind::Static(hir::MutImmutable) => Mode::Static,
hir::BodyOwnerKind::Static(hir::MutMutable) => Mode::StaticMut,
};
debug!("run_pass: mode={:?}", mode);
if mode == Mode::Fn || mode == Mode::ConstFn {
// This is ugly because Qualifier holds onto mir,
// which can't be mutated until its scope ends.
let (temps, candidates) = {
let mut qualifier = Qualifier::new(tcx, def_id, mir, mode);
if mode == Mode::ConstFn {
if tcx.is_min_const_fn(def_id) {
// enforce `min_const_fn` for stable const fns
use super::qualify_min_const_fn::is_min_const_fn;
if let Err((span, err)) = is_min_const_fn(tcx, def_id, mir) {
tcx.sess.span_err(span, &err);
} else {
// this should not produce any errors, but better safe than sorry
// FIXME(#53819)
qualifier.qualify_const();
}
} else {
// Enforce a constant-like CFG for `const fn`.
qualifier.qualify_const();
}
} else {
while let Some((bb, data)) = qualifier.rpo.next() {
qualifier.visit_basic_block_data(bb, data);
}
}
(qualifier.temp_promotion_state, qualifier.promotion_candidates)
};
// Do the actual promotion, now that we know what's viable.
promote_consts::promote_candidates(mir, tcx, temps, candidates);
} else {
let promoted_temps = if mode == Mode::Const {
// Already computed by `mir_const_qualif`.
const_promoted_temps.unwrap()
} else {
Qualifier::new(tcx, def_id, mir, mode).qualify_const().1
};
// In `const` and `static` everything without `StorageDead`
// is `'static`, we don't have to create promoted MIR fragments,
// just remove `Drop` and `StorageDead` on "promoted" locals.
debug!("run_pass: promoted_temps={:?}", promoted_temps);
for block in mir.basic_blocks_mut() {
block.statements.retain(|statement| {
match statement.kind {
StatementKind::StorageDead(index) => {
!promoted_temps.contains(index)
}
_ => true
}
});
let terminator = block.terminator_mut();
match terminator.kind {
TerminatorKind::Drop { location: Place::Local(index), target, .. } => {
if promoted_temps.contains(index) {
terminator.kind = TerminatorKind::Goto {
target,
};
}
}
_ => {}
}
}
}
// Statics must be Sync.
if mode == Mode::Static {
// `#[thread_local]` statics don't have to be `Sync`.
for attr in &tcx.get_attrs(def_id)[..] {
if attr.check_name("thread_local") {
return;
}
}
let ty = mir.return_ty();
tcx.infer_ctxt().enter(|infcx| {
let param_env = ty::ParamEnv::empty();
let cause = traits::ObligationCause::new(mir.span, id, traits::SharedStatic);
let mut fulfillment_cx = traits::FulfillmentContext::new();
fulfillment_cx.register_bound(&infcx,
param_env,
ty,
tcx.require_lang_item(lang_items::SyncTraitLangItem),
cause);
if let Err(err) = fulfillment_cx.select_all_or_error(&infcx) {
infcx.report_fulfillment_errors(&err, None, false);
}
});
}
}
}
fn args_required_const(tcx: TyCtxt, def_id: DefId) -> Option<FxHashSet<usize>> {
let attrs = tcx.get_attrs(def_id);
let attr = attrs.iter().find(|a| a.check_name("rustc_args_required_const"))?;
let mut ret = FxHashSet::default();
for meta in attr.meta_item_list()? {
match meta.literal()?.node {
LitKind::Int(a, _) => { ret.insert(a as usize); }
_ => return None,
}
}
Some(ret)
}