summaryrefslogtreecommitdiff
path: root/compiler/rustc_mir_transform/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_mir_transform/src')
-rw-r--r--compiler/rustc_mir_transform/src/check_const_item_mutation.rs65
-rw-r--r--compiler/rustc_mir_transform/src/check_packed_ref.rs23
-rw-r--r--compiler/rustc_mir_transform/src/check_unsafety.rs70
-rw-r--r--compiler/rustc_mir_transform/src/const_prop.rs18
-rw-r--r--compiler/rustc_mir_transform/src/const_prop_lint.rs76
-rw-r--r--compiler/rustc_mir_transform/src/copy_prop.rs35
-rw-r--r--compiler/rustc_mir_transform/src/coverage/query.rs2
-rw-r--r--compiler/rustc_mir_transform/src/dataflow_const_prop.rs34
-rw-r--r--compiler/rustc_mir_transform/src/deduce_param_attrs.rs34
-rw-r--r--compiler/rustc_mir_transform/src/errors.rs245
-rw-r--r--compiler/rustc_mir_transform/src/ffi_unwind_calls.rs18
-rw-r--r--compiler/rustc_mir_transform/src/function_item_references.rs33
-rw-r--r--compiler/rustc_mir_transform/src/generator.rs43
-rw-r--r--compiler/rustc_mir_transform/src/inline.rs27
-rw-r--r--compiler/rustc_mir_transform/src/instsimplify.rs13
-rw-r--r--compiler/rustc_mir_transform/src/lib.rs12
-rw-r--r--compiler/rustc_mir_transform/src/lower_intrinsics.rs10
-rw-r--r--compiler/rustc_mir_transform/src/normalize_array_len.rs7
-rw-r--r--compiler/rustc_mir_transform/src/nrvo.rs3
-rw-r--r--compiler/rustc_mir_transform/src/ref_prop.rs408
-rw-r--r--compiler/rustc_mir_transform/src/shim.rs2
-rw-r--r--compiler/rustc_mir_transform/src/ssa.rs247
22 files changed, 1058 insertions, 367 deletions
diff --git a/compiler/rustc_mir_transform/src/check_const_item_mutation.rs b/compiler/rustc_mir_transform/src/check_const_item_mutation.rs
index 57b24c9c552..b79150737d6 100644
--- a/compiler/rustc_mir_transform/src/check_const_item_mutation.rs
+++ b/compiler/rustc_mir_transform/src/check_const_item_mutation.rs
@@ -1,11 +1,12 @@
-use rustc_errors::{DiagnosticBuilder, DiagnosticMessage};
+use rustc_hir::HirId;
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::*;
use rustc_middle::ty::TyCtxt;
use rustc_session::lint::builtin::CONST_ITEM_MUTATION;
use rustc_span::def_id::DefId;
+use rustc_span::Span;
-use crate::MirLint;
+use crate::{errors, MirLint};
pub struct CheckConstItemMutation;
@@ -58,16 +59,14 @@ impl<'tcx> ConstMutationChecker<'_, 'tcx> {
}
}
- fn lint_const_item_usage(
+ /// If we should lint on this usage, return the [`HirId`], source [`Span`]
+ /// and [`Span`] of the const item to use in the lint.
+ fn should_lint_const_item_usage(
&self,
place: &Place<'tcx>,
const_item: DefId,
location: Location,
- msg: impl Into<DiagnosticMessage>,
- decorate: impl for<'a, 'b> FnOnce(
- &'a mut DiagnosticBuilder<'b, ()>,
- ) -> &'a mut DiagnosticBuilder<'b, ()>,
- ) {
+ ) -> Option<(HirId, Span, Span)> {
// Don't lint on borrowing/assigning when a dereference is involved.
// If we 'leave' the temporary via a dereference, we must
// be modifying something else
@@ -83,16 +82,9 @@ impl<'tcx> ConstMutationChecker<'_, 'tcx> {
.assert_crate_local()
.lint_root;
- self.tcx.struct_span_lint_hir(
- CONST_ITEM_MUTATION,
- lint_root,
- source_info.span,
- msg,
- |lint| {
- decorate(lint)
- .span_note(self.tcx.def_span(const_item), "`const` item defined here")
- },
- );
+ Some((lint_root, source_info.span, self.tcx.def_span(const_item)))
+ } else {
+ None
}
}
}
@@ -104,10 +96,14 @@ impl<'tcx> Visitor<'tcx> for ConstMutationChecker<'_, 'tcx> {
// Assigning directly to a constant (e.g. `FOO = true;`) is a hard error,
// so emitting a lint would be redundant.
if !lhs.projection.is_empty() {
- if let Some(def_id) = self.is_const_item_without_destructor(lhs.local) {
- self.lint_const_item_usage(&lhs, def_id, loc, "attempting to modify a `const` item",|lint| {
- lint.note("each usage of a `const` item creates a new temporary; the original `const` item will not be modified")
- })
+ if let Some(def_id) = self.is_const_item_without_destructor(lhs.local)
+ && let Some((lint_root, span, item)) = self.should_lint_const_item_usage(&lhs, def_id, loc) {
+ self.tcx.emit_spanned_lint(
+ CONST_ITEM_MUTATION,
+ lint_root,
+ span,
+ errors::ConstMutate::Modify { konst: item }
+ );
}
}
// We are looking for MIR of the form:
@@ -143,17 +139,22 @@ impl<'tcx> Visitor<'tcx> for ConstMutationChecker<'_, 'tcx> {
});
let lint_loc =
if method_did.is_some() { self.body.terminator_loc(loc.block) } else { loc };
- self.lint_const_item_usage(place, def_id, lint_loc, "taking a mutable reference to a `const` item", |lint| {
- lint
- .note("each usage of a `const` item creates a new temporary")
- .note("the mutable reference will refer to this temporary, not the original `const` item");
-
- if let Some((method_did, _substs)) = method_did {
- lint.span_note(self.tcx.def_span(method_did), "mutable reference created due to call to this method");
- }
- lint
- });
+ let method_call = if let Some((method_did, _)) = method_did {
+ Some(self.tcx.def_span(method_did))
+ } else {
+ None
+ };
+ if let Some((lint_root, span, item)) =
+ self.should_lint_const_item_usage(place, def_id, lint_loc)
+ {
+ self.tcx.emit_spanned_lint(
+ CONST_ITEM_MUTATION,
+ lint_root,
+ span,
+ errors::ConstMutate::MutBorrow { method_call, konst: item },
+ );
+ }
}
}
self.super_rvalue(rvalue, loc);
diff --git a/compiler/rustc_mir_transform/src/check_packed_ref.rs b/compiler/rustc_mir_transform/src/check_packed_ref.rs
index b9bc89fcf8f..2e6cf603d59 100644
--- a/compiler/rustc_mir_transform/src/check_packed_ref.rs
+++ b/compiler/rustc_mir_transform/src/check_packed_ref.rs
@@ -1,10 +1,9 @@
-use rustc_errors::struct_span_err;
use rustc_middle::mir::visit::{PlaceContext, Visitor};
use rustc_middle::mir::*;
use rustc_middle::ty::{self, TyCtxt};
-use crate::util;
use crate::MirLint;
+use crate::{errors, util};
pub struct CheckPackedRef;
@@ -49,25 +48,7 @@ impl<'tcx> Visitor<'tcx> for PackedRefChecker<'_, 'tcx> {
// shouldn't do.
span_bug!(self.source_info.span, "builtin derive created an unaligned reference");
} else {
- struct_span_err!(
- self.tcx.sess,
- self.source_info.span,
- E0793,
- "reference to packed field is unaligned"
- )
- .note(
- "packed structs are only aligned by one byte, and many modern architectures \
- penalize unaligned field accesses"
- )
- .note(
- "creating a misaligned reference is undefined behavior (even if that \
- reference is never dereferenced)",
- ).help(
- "copy the field contents to a local variable, or replace the \
- reference with a raw pointer and use `read_unaligned`/`write_unaligned` \
- (loads and stores via `*p` must be properly aligned even when using raw pointers)"
- )
- .emit();
+ self.tcx.sess.emit_err(errors::UnalignedPackedRef { span: self.source_info.span });
}
}
}
diff --git a/compiler/rustc_mir_transform/src/check_unsafety.rs b/compiler/rustc_mir_transform/src/check_unsafety.rs
index ce6d865a7dc..069514d8a3b 100644
--- a/compiler/rustc_mir_transform/src/check_unsafety.rs
+++ b/compiler/rustc_mir_transform/src/check_unsafety.rs
@@ -1,5 +1,4 @@
use rustc_data_structures::unord::{UnordItems, UnordSet};
-use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId};
@@ -8,13 +7,15 @@ use rustc_hir::intravisit;
use rustc_hir::{BlockCheckMode, ExprKind, Node};
use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::*;
-use rustc_middle::ty::query::Providers;
+use rustc_middle::query::Providers;
use rustc_middle::ty::{self, TyCtxt};
use rustc_session::lint::builtin::{UNSAFE_OP_IN_UNSAFE_FN, UNUSED_UNSAFE};
use rustc_session::lint::Level;
use std::ops::Bound;
+use crate::errors;
+
pub struct UnsafetyChecker<'a, 'tcx> {
body: &'a Body<'tcx>,
body_did: LocalDefId,
@@ -509,21 +510,12 @@ fn unsafety_check_result(tcx: TyCtxt<'_>, def: LocalDefId) -> &UnsafetyCheckResu
fn report_unused_unsafe(tcx: TyCtxt<'_>, kind: UnusedUnsafe, id: HirId) {
let span = tcx.sess.source_map().guess_head_span(tcx.hir().span(id));
- let msg = "unnecessary `unsafe` block";
- tcx.struct_span_lint_hir(UNUSED_UNSAFE, id, span, msg, |lint| {
- lint.span_label(span, msg);
- match kind {
- UnusedUnsafe::Unused => {}
- UnusedUnsafe::InUnsafeBlock(id) => {
- lint.span_label(
- tcx.sess.source_map().guess_head_span(tcx.hir().span(id)),
- "because it's nested under this `unsafe` block",
- );
- }
- }
-
- lint
- });
+ let nested_parent = if let UnusedUnsafe::InUnsafeBlock(id) = kind {
+ Some(tcx.sess.source_map().guess_head_span(tcx.hir().span(id)))
+ } else {
+ None
+ };
+ tcx.emit_spanned_lint(UNUSED_UNSAFE, id, span, errors::UnusedUnsafe { span, nested_parent });
}
pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) {
@@ -537,26 +529,11 @@ pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) {
let UnsafetyCheckResult { violations, unused_unsafes, .. } = tcx.unsafety_check_result(def_id);
for &UnsafetyViolation { source_info, lint_root, kind, details } in violations.iter() {
- let (description, note) = details.description_and_note();
+ let details = errors::RequiresUnsafeDetail { violation: details, span: source_info.span };
match kind {
UnsafetyViolationKind::General => {
- // once
- let unsafe_fn_msg = if unsafe_op_in_unsafe_fn_allowed(tcx, lint_root) {
- " function or"
- } else {
- ""
- };
-
- let mut err = struct_span_err!(
- tcx.sess,
- source_info.span,
- E0133,
- "{} is unsafe and requires unsafe{} block",
- description,
- unsafe_fn_msg,
- );
- err.span_label(source_info.span, description).note(note);
+ let op_in_unsafe_fn_allowed = unsafe_op_in_unsafe_fn_allowed(tcx, lint_root);
let note_non_inherited = tcx.hir().parent_iter(lint_root).find(|(id, node)| {
if let Node::Expr(block) = node
&& let ExprKind::Block(block, _) = block.kind
@@ -572,22 +549,23 @@ pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) {
false
}
});
- if let Some((id, _)) = note_non_inherited {
- let span = tcx.hir().span(id);
- err.span_label(
- tcx.sess.source_map().guess_head_span(span),
- "items do not inherit unsafety from separate enclosing items",
- );
- }
-
- err.emit();
+ let enclosing = if let Some((id, _)) = note_non_inherited {
+ Some(tcx.sess.source_map().guess_head_span(tcx.hir().span(id)))
+ } else {
+ None
+ };
+ tcx.sess.emit_err(errors::RequiresUnsafe {
+ span: source_info.span,
+ enclosing,
+ details,
+ op_in_unsafe_fn_allowed,
+ });
}
- UnsafetyViolationKind::UnsafeFn => tcx.struct_span_lint_hir(
+ UnsafetyViolationKind::UnsafeFn => tcx.emit_spanned_lint(
UNSAFE_OP_IN_UNSAFE_FN,
lint_root,
source_info.span,
- format!("{} is unsafe and requires unsafe block (error E0133)", description,),
- |lint| lint.span_label(source_info.span, description).note(note),
+ errors::UnsafeOpInUnsafeFn { details },
),
}
}
diff --git a/compiler/rustc_mir_transform/src/const_prop.rs b/compiler/rustc_mir_transform/src/const_prop.rs
index 7f995c69a48..a5d18fff89b 100644
--- a/compiler/rustc_mir_transform/src/const_prop.rs
+++ b/compiler/rustc_mir_transform/src/const_prop.rs
@@ -806,6 +806,24 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> {
}
}
+ fn process_projection_elem(
+ &mut self,
+ elem: PlaceElem<'tcx>,
+ _: Location,
+ ) -> Option<PlaceElem<'tcx>> {
+ if let PlaceElem::Index(local) = elem
+ && let Some(value) = self.get_const(local.into())
+ && self.should_const_prop(&value)
+ && let interpret::Operand::Immediate(interpret::Immediate::Scalar(scalar)) = *value
+ && let Ok(offset) = scalar.to_target_usize(&self.tcx)
+ && let Some(min_length) = offset.checked_add(1)
+ {
+ Some(PlaceElem::ConstantIndex { offset, min_length, from_end: false })
+ } else {
+ None
+ }
+ }
+
fn visit_assign(
&mut self,
place: &mut Place<'tcx>,
diff --git a/compiler/rustc_mir_transform/src/const_prop_lint.rs b/compiler/rustc_mir_transform/src/const_prop_lint.rs
index a4049d08d7b..adb09c509d2 100644
--- a/compiler/rustc_mir_transform/src/const_prop_lint.rs
+++ b/compiler/rustc_mir_transform/src/const_prop_lint.rs
@@ -1,6 +1,8 @@
//! Propagates constants for early reporting of statically known
//! assertion failures
+use std::fmt::Debug;
+
use either::Left;
use rustc_const_eval::interpret::Immediate;
@@ -17,7 +19,6 @@ use rustc_middle::ty::InternalSubsts;
use rustc_middle::ty::{
self, ConstInt, Instance, ParamEnv, ScalarInt, Ty, TyCtxt, TypeVisitableExt,
};
-use rustc_session::lint;
use rustc_span::Span;
use rustc_target::abi::{HasDataLayout, Size, TargetDataLayout};
use rustc_trait_selection::traits;
@@ -25,6 +26,7 @@ use rustc_trait_selection::traits;
use crate::const_prop::CanConstProp;
use crate::const_prop::ConstPropMachine;
use crate::const_prop::ConstPropMode;
+use crate::errors::AssertLint;
use crate::MirLint;
/// The maximum number of bytes that we'll allocate space for a local or the return value.
@@ -311,18 +313,9 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
}
}
- fn report_assert_as_lint(
- &self,
- lint: &'static lint::Lint,
- location: Location,
- message: &'static str,
- panic: AssertKind<impl std::fmt::Debug>,
- ) {
- let source_info = self.body().source_info(location);
+ fn report_assert_as_lint(&self, source_info: &SourceInfo, lint: AssertLint<impl Debug>) {
if let Some(lint_root) = self.lint_root(*source_info) {
- self.tcx.struct_span_lint_hir(lint, lint_root, source_info.span, message, |lint| {
- lint.span_label(source_info.span, format!("{:?}", panic))
- });
+ self.tcx.emit_spanned_lint(lint.lint(), lint_root, source_info.span, lint);
}
}
@@ -335,11 +328,13 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
// `AssertKind` only has an `OverflowNeg` variant, so make sure that is
// appropriate to use.
assert_eq!(op, UnOp::Neg, "Neg is the only UnOp that can overflow");
+ let source_info = self.body().source_info(location);
self.report_assert_as_lint(
- lint::builtin::ARITHMETIC_OVERFLOW,
- location,
- "this arithmetic operation will overflow",
- AssertKind::OverflowNeg(val.to_const_int()),
+ source_info,
+ AssertLint::ArithmeticOverflow(
+ source_info.span,
+ AssertKind::OverflowNeg(val.to_const_int()),
+ ),
);
return None;
}
@@ -370,23 +365,23 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
let r_bits = r.to_scalar().to_bits(right_size).ok();
if r_bits.map_or(false, |b| b >= left_size.bits() as u128) {
debug!("check_binary_op: reporting assert for {:?}", location);
+ let source_info = self.body().source_info(location);
+ let panic = AssertKind::Overflow(
+ op,
+ match l {
+ Some(l) => l.to_const_int(),
+ // Invent a dummy value, the diagnostic ignores it anyway
+ None => ConstInt::new(
+ ScalarInt::try_from_uint(1_u8, left_size).unwrap(),
+ left_ty.is_signed(),
+ left_ty.is_ptr_sized_integral(),
+ ),
+ },
+ r.to_const_int(),
+ );
self.report_assert_as_lint(
- lint::builtin::ARITHMETIC_OVERFLOW,
- location,
- "this arithmetic operation will overflow",
- AssertKind::Overflow(
- op,
- match l {
- Some(l) => l.to_const_int(),
- // Invent a dummy value, the diagnostic ignores it anyway
- None => ConstInt::new(
- ScalarInt::try_from_uint(1_u8, left_size).unwrap(),
- left_ty.is_signed(),
- left_ty.is_ptr_sized_integral(),
- ),
- },
- r.to_const_int(),
- ),
+ source_info,
+ AssertLint::ArithmeticOverflow(source_info.span, panic),
);
return None;
}
@@ -398,11 +393,13 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, &l, &r)?;
Ok(overflow)
})? {
+ let source_info = self.body().source_info(location);
self.report_assert_as_lint(
- lint::builtin::ARITHMETIC_OVERFLOW,
- location,
- "this arithmetic operation will overflow",
- AssertKind::Overflow(op, l.to_const_int(), r.to_const_int()),
+ source_info,
+ AssertLint::ArithmeticOverflow(
+ source_info.span,
+ AssertKind::Overflow(op, l.to_const_int(), r.to_const_int()),
+ ),
);
return None;
}
@@ -543,11 +540,10 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
// Need proper const propagator for these.
_ => return None,
};
+ let source_info = self.body().source_info(location);
self.report_assert_as_lint(
- lint::builtin::UNCONDITIONAL_PANIC,
- location,
- "this operation will panic at runtime",
- msg,
+ source_info,
+ AssertLint::UnconditionalPanic(source_info.span, msg),
);
}
diff --git a/compiler/rustc_mir_transform/src/copy_prop.rs b/compiler/rustc_mir_transform/src/copy_prop.rs
index 3922ed2fbf7..319f3a79705 100644
--- a/compiler/rustc_mir_transform/src/copy_prop.rs
+++ b/compiler/rustc_mir_transform/src/copy_prop.rs
@@ -33,9 +33,8 @@ impl<'tcx> MirPass<'tcx> for CopyProp {
}
fn propagate_ssa<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
- let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
let borrowed_locals = borrowed_locals(body);
- let ssa = SsaLocals::new(tcx, param_env, body, &borrowed_locals);
+ let ssa = SsaLocals::new(body);
let fully_moved = fully_moved_locals(&ssa, body);
debug!(?fully_moved);
@@ -76,7 +75,7 @@ fn propagate_ssa<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
fn fully_moved_locals(ssa: &SsaLocals, body: &Body<'_>) -> BitSet<Local> {
let mut fully_moved = BitSet::new_filled(body.local_decls.len());
- for (_, rvalue) in ssa.assignments(body) {
+ for (_, rvalue, _) in ssa.assignments(body) {
let (Rvalue::Use(Operand::Copy(place) | Operand::Move(place)) | Rvalue::CopyForDeref(place))
= rvalue
else { continue };
@@ -163,20 +162,22 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
}
fn visit_statement(&mut self, stmt: &mut Statement<'tcx>, loc: Location) {
- match stmt.kind {
- // When removing storage statements, we need to remove both (#107511).
- StatementKind::StorageLive(l) | StatementKind::StorageDead(l)
- if self.storage_to_remove.contains(l) =>
- {
- stmt.make_nop()
- }
- StatementKind::Assign(box (ref place, ref mut rvalue))
- if place.as_local().is_some() =>
- {
- // Do not replace assignments.
- self.visit_rvalue(rvalue, loc)
- }
- _ => self.super_statement(stmt, loc),
+ // When removing storage statements, we need to remove both (#107511).
+ if let StatementKind::StorageLive(l) | StatementKind::StorageDead(l) = stmt.kind
+ && self.storage_to_remove.contains(l)
+ {
+ stmt.make_nop();
+ return
+ }
+
+ self.super_statement(stmt, loc);
+
+ // Do not leave tautological assignments around.
+ if let StatementKind::Assign(box (lhs, ref rhs)) = stmt.kind
+ && let Rvalue::Use(Operand::Copy(rhs) | Operand::Move(rhs)) | Rvalue::CopyForDeref(rhs) = *rhs
+ && lhs == rhs
+ {
+ stmt.make_nop();
}
}
}
diff --git a/compiler/rustc_mir_transform/src/coverage/query.rs b/compiler/rustc_mir_transform/src/coverage/query.rs
index bf01b45eb40..74b4b4a07c5 100644
--- a/compiler/rustc_mir_transform/src/coverage/query.rs
+++ b/compiler/rustc_mir_transform/src/coverage/query.rs
@@ -2,7 +2,7 @@ use super::*;
use rustc_middle::mir::coverage::*;
use rustc_middle::mir::{self, Body, Coverage, CoverageInfo};
-use rustc_middle::ty::query::Providers;
+use rustc_middle::query::Providers;
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::def_id::DefId;
diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs
index 254b704f9fc..7adfc9dff2a 100644
--- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs
+++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs
@@ -79,22 +79,22 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
&self.map
}
- fn handle_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>) {
- match statement.kind {
- StatementKind::SetDiscriminant { box ref place, variant_index } => {
- state.flood_discr(place.as_ref(), &self.map);
- if self.map.find_discr(place.as_ref()).is_some() {
- let enum_ty = place.ty(self.local_decls, self.tcx).ty;
- if let Some(discr) = self.eval_discriminant(enum_ty, variant_index) {
- state.assign_discr(
- place.as_ref(),
- ValueOrPlace::Value(FlatSet::Elem(discr)),
- &self.map,
- );
- }
- }
+ fn handle_set_discriminant(
+ &self,
+ place: Place<'tcx>,
+ variant_index: VariantIdx,
+ state: &mut State<Self::Value>,
+ ) {
+ state.flood_discr(place.as_ref(), &self.map);
+ if self.map.find_discr(place.as_ref()).is_some() {
+ let enum_ty = place.ty(self.local_decls, self.tcx).ty;
+ if let Some(discr) = self.eval_discriminant(enum_ty, variant_index) {
+ state.assign_discr(
+ place.as_ref(),
+ ValueOrPlace::Value(FlatSet::Elem(discr)),
+ &self.map,
+ );
}
- _ => self.super_statement(statement, state),
}
}
@@ -208,8 +208,8 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
_ => unreachable!(),
}
.map(|result| ValueOrPlace::Value(self.wrap_immediate(result, *ty)))
- .unwrap_or(ValueOrPlace::top()),
- _ => ValueOrPlace::top(),
+ .unwrap_or(ValueOrPlace::TOP),
+ _ => ValueOrPlace::TOP,
},
Rvalue::BinaryOp(op, box (left, right)) => {
// Overflows must be ignored here.
diff --git a/compiler/rustc_mir_transform/src/deduce_param_attrs.rs b/compiler/rustc_mir_transform/src/deduce_param_attrs.rs
index 8ee08c5be34..a133c9d4782 100644
--- a/compiler/rustc_mir_transform/src/deduce_param_attrs.rs
+++ b/compiler/rustc_mir_transform/src/deduce_param_attrs.rs
@@ -8,7 +8,7 @@
use rustc_hir::def_id::LocalDefId;
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
-use rustc_middle::mir::{Body, Local, Location, Operand, Terminator, TerminatorKind, RETURN_PLACE};
+use rustc_middle::mir::{Body, Location, Operand, Place, Terminator, TerminatorKind, RETURN_PLACE};
use rustc_middle::ty::{self, DeducedParamAttrs, Ty, TyCtxt};
use rustc_session::config::OptLevel;
@@ -29,31 +29,31 @@ impl DeduceReadOnly {
}
impl<'tcx> Visitor<'tcx> for DeduceReadOnly {
- fn visit_local(&mut self, local: Local, mut context: PlaceContext, _: Location) {
+ fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
// We're only interested in arguments.
- if local == RETURN_PLACE || local.index() > self.mutable_args.domain_size() {
+ if place.local == RETURN_PLACE || place.local.index() > self.mutable_args.domain_size() {
return;
}
- // Replace place contexts that are moves with copies. This is safe in all cases except
- // function argument position, which we already handled in `visit_terminator()` by using the
- // ArgumentChecker. See the comment in that method for more details.
- //
- // In the future, we might want to move this out into a separate pass, but for now let's
- // just do it on the fly because that's faster.
- if matches!(context, PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)) {
- context = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy);
- }
-
- match context {
- PlaceContext::MutatingUse(..)
- | PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) => {
+ let mark_as_mutable = match context {
+ PlaceContext::MutatingUse(..) => {
// This is a mutation, so mark it as such.
- self.mutable_args.insert(local.index() - 1);
+ true
+ }
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::AddressOf) => {
+ // Whether mutating though a `&raw const` is allowed is still undecided, so we
+ // disable any sketchy `readonly` optimizations for now.
+ // But we only need to do this if the pointer would point into the argument.
+ !place.is_indirect()
}
PlaceContext::NonMutatingUse(..) | PlaceContext::NonUse(..) => {
// Not mutating, so it's fine.
+ false
}
+ };
+
+ if mark_as_mutable {
+ self.mutable_args.insert(place.local.index() - 1);
}
}
diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs
new file mode 100644
index 00000000000..602e40d5131
--- /dev/null
+++ b/compiler/rustc_mir_transform/src/errors.rs
@@ -0,0 +1,245 @@
+use rustc_errors::{
+ DecorateLint, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, Handler, IntoDiagnostic,
+};
+use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
+use rustc_middle::mir::{AssertKind, UnsafetyViolationDetails};
+use rustc_session::lint::{self, Lint};
+use rustc_span::Span;
+
+#[derive(LintDiagnostic)]
+pub(crate) enum ConstMutate {
+ #[diag(mir_transform_const_modify)]
+ #[note]
+ Modify {
+ #[note(mir_transform_const_defined_here)]
+ konst: Span,
+ },
+ #[diag(mir_transform_const_mut_borrow)]
+ #[note]
+ #[note(mir_transform_note2)]
+ MutBorrow {
+ #[note(mir_transform_note3)]
+ method_call: Option<Span>,
+ #[note(mir_transform_const_defined_here)]
+ konst: Span,
+ },
+}
+
+#[derive(Diagnostic)]
+#[diag(mir_transform_unaligned_packed_ref, code = "E0793")]
+#[note]
+#[note(mir_transform_note_ub)]
+#[help]
+pub(crate) struct UnalignedPackedRef {
+ #[primary_span]
+ pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(mir_transform_unused_unsafe)]
+pub(crate) struct UnusedUnsafe {
+ #[label(mir_transform_unused_unsafe)]
+ pub span: Span,
+ #[label]
+ pub nested_parent: Option<Span>,
+}
+
+pub(crate) struct RequiresUnsafe {
+ pub span: Span,
+ pub details: RequiresUnsafeDetail,
+ pub enclosing: Option<Span>,
+ pub op_in_unsafe_fn_allowed: bool,
+}
+
+// The primary message for this diagnostic should be '{$label} is unsafe and...',
+// so we need to eagerly translate the label here, which isn't supported by the derive API
+// We could also exhaustively list out the primary messages for all unsafe violations,
+// but this would result in a lot of duplication.
+impl<'sess, G: EmissionGuarantee> IntoDiagnostic<'sess, G> for RequiresUnsafe {
+ #[track_caller]
+ fn into_diagnostic(self, handler: &'sess Handler) -> DiagnosticBuilder<'sess, G> {
+ let mut diag =
+ handler.struct_diagnostic(crate::fluent_generated::mir_transform_requires_unsafe);
+ diag.code(rustc_errors::DiagnosticId::Error("E0133".to_string()));
+ diag.set_span(self.span);
+ diag.span_label(self.span, self.details.label());
+ diag.note(self.details.note());
+ let desc = handler.eagerly_translate_to_string(self.details.label(), [].into_iter());
+ diag.set_arg("details", desc);
+ diag.set_arg("op_in_unsafe_fn_allowed", self.op_in_unsafe_fn_allowed);
+ if let Some(sp) = self.enclosing {
+ diag.span_label(sp, crate::fluent_generated::mir_transform_not_inherited);
+ }
+ diag
+ }
+}
+
+#[derive(Copy, Clone)]
+pub(crate) struct RequiresUnsafeDetail {
+ pub span: Span,
+ pub violation: UnsafetyViolationDetails,
+}
+
+impl RequiresUnsafeDetail {
+ fn note(self) -> DiagnosticMessage {
+ use UnsafetyViolationDetails::*;
+ match self.violation {
+ CallToUnsafeFunction => crate::fluent_generated::mir_transform_call_to_unsafe_note,
+ UseOfInlineAssembly => crate::fluent_generated::mir_transform_use_of_asm_note,
+ InitializingTypeWith => {
+ crate::fluent_generated::mir_transform_initializing_valid_range_note
+ }
+ CastOfPointerToInt => crate::fluent_generated::mir_transform_const_ptr2int_note,
+ UseOfMutableStatic => crate::fluent_generated::mir_transform_use_of_static_mut_note,
+ UseOfExternStatic => crate::fluent_generated::mir_transform_use_of_extern_static_note,
+ DerefOfRawPointer => crate::fluent_generated::mir_transform_deref_ptr_note,
+ AccessToUnionField => crate::fluent_generated::mir_transform_union_access_note,
+ MutationOfLayoutConstrainedField => {
+ crate::fluent_generated::mir_transform_mutation_layout_constrained_note
+ }
+ BorrowOfLayoutConstrainedField => {
+ crate::fluent_generated::mir_transform_mutation_layout_constrained_borrow_note
+ }
+ CallToFunctionWith => crate::fluent_generated::mir_transform_target_feature_call_note,
+ }
+ }
+
+ fn label(self) -> DiagnosticMessage {
+ use UnsafetyViolationDetails::*;
+ match self.violation {
+ CallToUnsafeFunction => crate::fluent_generated::mir_transform_call_to_unsafe_label,
+ UseOfInlineAssembly => crate::fluent_generated::mir_transform_use_of_asm_label,
+ InitializingTypeWith => {
+ crate::fluent_generated::mir_transform_initializing_valid_range_label
+ }
+ CastOfPointerToInt => crate::fluent_generated::mir_transform_const_ptr2int_label,
+ UseOfMutableStatic => crate::fluent_generated::mir_transform_use_of_static_mut_label,
+ UseOfExternStatic => crate::fluent_generated::mir_transform_use_of_extern_static_label,
+ DerefOfRawPointer => crate::fluent_generated::mir_transform_deref_ptr_label,
+ AccessToUnionField => crate::fluent_generated::mir_transform_union_access_label,
+ MutationOfLayoutConstrainedField => {
+ crate::fluent_generated::mir_transform_mutation_layout_constrained_label
+ }
+ BorrowOfLayoutConstrainedField => {
+ crate::fluent_generated::mir_transform_mutation_layout_constrained_borrow_label
+ }
+ CallToFunctionWith => crate::fluent_generated::mir_transform_target_feature_call_label,
+ }
+ }
+}
+
+pub(crate) struct UnsafeOpInUnsafeFn {
+ pub details: RequiresUnsafeDetail,
+}
+
+impl<'a> DecorateLint<'a, ()> for UnsafeOpInUnsafeFn {
+ #[track_caller]
+ fn decorate_lint<'b>(
+ self,
+ diag: &'b mut DiagnosticBuilder<'a, ()>,
+ ) -> &'b mut DiagnosticBuilder<'a, ()> {
+ let desc = diag
+ .handler()
+ .expect("lint should not yet be emitted")
+ .eagerly_translate_to_string(self.details.label(), [].into_iter());
+ diag.set_arg("details", desc);
+ diag.span_label(self.details.span, self.details.label());
+ diag.note(self.details.note());
+ diag
+ }
+
+ fn msg(&self) -> DiagnosticMessage {
+ crate::fluent_generated::mir_transform_unsafe_op_in_unsafe_fn
+ }
+}
+
+pub(crate) enum AssertLint<P> {
+ ArithmeticOverflow(Span, AssertKind<P>),
+ UnconditionalPanic(Span, AssertKind<P>),
+}
+
+impl<'a, P: std::fmt::Debug> DecorateLint<'a, ()> for AssertLint<P> {
+ fn decorate_lint<'b>(
+ self,
+ diag: &'b mut DiagnosticBuilder<'a, ()>,
+ ) -> &'b mut DiagnosticBuilder<'a, ()> {
+ diag.span_label(self.span(), format!("{:?}", self.panic()));
+ diag
+ }
+
+ fn msg(&self) -> DiagnosticMessage {
+ match self {
+ AssertLint::ArithmeticOverflow(..) => {
+ crate::fluent_generated::mir_transform_arithmetic_overflow
+ }
+ AssertLint::UnconditionalPanic(..) => {
+ crate::fluent_generated::mir_transform_operation_will_panic
+ }
+ }
+ }
+}
+
+impl<P> AssertLint<P> {
+ pub fn lint(&self) -> &'static Lint {
+ match self {
+ AssertLint::ArithmeticOverflow(..) => lint::builtin::ARITHMETIC_OVERFLOW,
+ AssertLint::UnconditionalPanic(..) => lint::builtin::UNCONDITIONAL_PANIC,
+ }
+ }
+ pub fn span(&self) -> Span {
+ match self {
+ AssertLint::ArithmeticOverflow(sp, _) | AssertLint::UnconditionalPanic(sp, _) => *sp,
+ }
+ }
+ pub fn panic(&self) -> &AssertKind<P> {
+ match self {
+ AssertLint::ArithmeticOverflow(_, p) | AssertLint::UnconditionalPanic(_, p) => p,
+ }
+ }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(mir_transform_ffi_unwind_call)]
+pub(crate) struct FfiUnwindCall {
+ #[label(mir_transform_ffi_unwind_call)]
+ pub span: Span,
+ pub foreign: bool,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(mir_transform_fn_item_ref)]
+pub(crate) struct FnItemRef {
+ #[suggestion(code = "{sugg}", applicability = "unspecified")]
+ pub span: Span,
+ pub sugg: String,
+ pub ident: String,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(mir_transform_must_not_suspend)]
+pub(crate) struct MustNotSupend<'a> {
+ #[label]
+ pub yield_sp: Span,
+ #[subdiagnostic]
+ pub reason: Option<MustNotSuspendReason>,
+ #[help]
+ pub src_sp: Span,
+ pub pre: &'a str,
+ pub def_path: String,
+ pub post: &'a str,
+}
+
+#[derive(Subdiagnostic)]
+#[note(mir_transform_note)]
+pub(crate) struct MustNotSuspendReason {
+ #[primary_span]
+ pub span: Span,
+ pub reason: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(mir_transform_simd_shuffle_last_const)]
+pub(crate) struct SimdShuffleLastConst {
+ #[primary_span]
+ pub span: Span,
+}
diff --git a/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs b/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs
index db68adc8bc9..58cc161ddcc 100644
--- a/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs
+++ b/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs
@@ -1,13 +1,15 @@
use rustc_hir::def_id::{LocalDefId, LOCAL_CRATE};
use rustc_middle::mir::*;
use rustc_middle::query::LocalCrate;
+use rustc_middle::query::Providers;
use rustc_middle::ty::layout;
-use rustc_middle::ty::query::Providers;
use rustc_middle::ty::{self, TyCtxt};
use rustc_session::lint::builtin::FFI_UNWIND_CALLS;
use rustc_target::spec::abi::Abi;
use rustc_target::spec::PanicStrategy;
+use crate::errors;
+
fn abi_can_unwind(abi: Abi) -> bool {
use Abi::*;
match abi {
@@ -107,13 +109,13 @@ fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool {
.lint_root;
let span = terminator.source_info.span;
- let msg = match fn_def_id {
- Some(_) => "call to foreign function with FFI-unwind ABI",
- None => "call to function pointer with FFI-unwind ABI",
- };
- tcx.struct_span_lint_hir(FFI_UNWIND_CALLS, lint_root, span, msg, |lint| {
- lint.span_label(span, msg)
- });
+ let foreign = fn_def_id.is_some();
+ tcx.emit_spanned_lint(
+ FFI_UNWIND_CALLS,
+ lint_root,
+ span,
+ errors::FfiUnwindCall { span, foreign },
+ );
tainted = true;
}
diff --git a/compiler/rustc_mir_transform/src/function_item_references.rs b/compiler/rustc_mir_transform/src/function_item_references.rs
index f26c6de9648..5989dbebf2d 100644
--- a/compiler/rustc_mir_transform/src/function_item_references.rs
+++ b/compiler/rustc_mir_transform/src/function_item_references.rs
@@ -1,5 +1,4 @@
use itertools::Itertools;
-use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::*;
@@ -8,7 +7,7 @@ use rustc_session::lint::builtin::FUNCTION_ITEM_REFERENCES;
use rustc_span::{symbol::sym, Span};
use rustc_target::spec::abi::Abi;
-use crate::MirLint;
+use crate::{errors, MirLint};
pub struct FunctionItemReferences;
@@ -174,27 +173,21 @@ impl<'tcx> FunctionItemRefChecker<'_, 'tcx> {
let num_args = fn_sig.inputs().map_bound(|inputs| inputs.len()).skip_binder();
let variadic = if fn_sig.c_variadic() { ", ..." } else { "" };
let ret = if fn_sig.output().skip_binder().is_unit() { "" } else { " -> _" };
- self.tcx.struct_span_lint_hir(
+ let sugg = format!(
+ "{} as {}{}fn({}{}){}",
+ if params.is_empty() { ident.clone() } else { format!("{}::<{}>", ident, params) },
+ unsafety,
+ abi,
+ vec!["_"; num_args].join(", "),
+ variadic,
+ ret,
+ );
+
+ self.tcx.emit_spanned_lint(
FUNCTION_ITEM_REFERENCES,
lint_root,
span,
- "taking a reference to a function item does not give a function pointer",
- |lint| {
- lint.span_suggestion(
- span,
- format!("cast `{}` to obtain a function pointer", ident),
- format!(
- "{} as {}{}fn({}{}){}",
- if params.is_empty() { ident } else { format!("{}::<{}>", ident, params) },
- unsafety,
- abi,
- vec!["_"; num_args].join(", "),
- variadic,
- ret,
- ),
- Applicability::Unspecified,
- )
- },
+ errors::FnItemRef { span, sugg, ident },
);
}
}
diff --git a/compiler/rustc_mir_transform/src/generator.rs b/compiler/rustc_mir_transform/src/generator.rs
index 9e16c400f14..891e446942e 100644
--- a/compiler/rustc_mir_transform/src/generator.rs
+++ b/compiler/rustc_mir_transform/src/generator.rs
@@ -51,6 +51,7 @@
//! Otherwise it drops all the values in scope at the last suspension point.
use crate::deref_separator::deref_finder;
+use crate::errors;
use crate::simplify;
use crate::MirPass;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -1396,7 +1397,7 @@ fn create_cases<'tcx>(
pub(crate) fn mir_generator_witnesses<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: LocalDefId,
-) -> GeneratorLayout<'tcx> {
+) -> Option<GeneratorLayout<'tcx>> {
assert!(tcx.sess.opts.unstable_opts.drop_tracking_mir);
let (body, _) = tcx.mir_promoted(def_id);
@@ -1409,6 +1410,7 @@ pub(crate) fn mir_generator_witnesses<'tcx>(
// Get the interior types and substs which typeck computed
let movable = match *gen_ty.kind() {
ty::Generator(_, _, movability) => movability == hir::Movability::Movable,
+ ty::Error(_) => return None,
_ => span_bug!(body.span, "unexpected generator type {}", gen_ty),
};
@@ -1424,7 +1426,7 @@ pub(crate) fn mir_generator_witnesses<'tcx>(
check_suspend_tys(tcx, &generator_layout, &body);
- generator_layout
+ Some(generator_layout)
}
impl<'tcx> MirPass<'tcx> for StateTransform {
@@ -1891,36 +1893,21 @@ fn check_must_not_suspend_def(
data: SuspendCheckData<'_>,
) -> bool {
if let Some(attr) = tcx.get_attr(def_id, sym::must_not_suspend) {
- let msg = rustc_errors::DelayDm(|| {
- format!(
- "{}`{}`{} held across a suspend point, but should not be",
- data.descr_pre,
- tcx.def_path_str(def_id),
- data.descr_post,
- )
+ let reason = attr.value_str().map(|s| errors::MustNotSuspendReason {
+ span: data.source_span,
+ reason: s.as_str().to_string(),
});
- tcx.struct_span_lint_hir(
+ tcx.emit_spanned_lint(
rustc_session::lint::builtin::MUST_NOT_SUSPEND,
hir_id,
data.source_span,
- msg,
- |lint| {
- // add span pointing to the offending yield/await
- lint.span_label(data.yield_span, "the value is held across this suspend point");
-
- // Add optional reason note
- if let Some(note) = attr.value_str() {
- // FIXME(guswynn): consider formatting this better
- lint.span_note(data.source_span, note.as_str());
- }
-
- // Add some quick suggestions on what to do
- // FIXME: can `drop` work as a suggestion here as well?
- lint.span_help(
- data.source_span,
- "consider using a block (`{ ... }`) \
- to shrink the value's scope, ending before the suspend point",
- )
+ errors::MustNotSupend {
+ yield_sp: data.yield_span,
+ reason,
+ src_sp: data.source_span,
+ pre: data.descr_pre,
+ def_path: tcx.def_path_str(def_id),
+ post: data.descr_post,
},
);
diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs
index ece20d8d3e6..12f955d46bd 100644
--- a/compiler/rustc_mir_transform/src/inline.rs
+++ b/compiler/rustc_mir_transform/src/inline.rs
@@ -7,6 +7,7 @@ use rustc_index::Idx;
use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
use rustc_middle::mir::visit::*;
use rustc_middle::mir::*;
+use rustc_middle::ty::TypeVisitableExt;
use rustc_middle::ty::{self, Instance, InstanceDef, ParamEnv, Ty, TyCtxt};
use rustc_session::config::OptLevel;
use rustc_span::{hygiene::ExpnKind, ExpnData, LocalExpnId, Span};
@@ -168,7 +169,7 @@ impl<'tcx> Inliner<'tcx> {
let callee_attrs = self.tcx.codegen_fn_attrs(callsite.callee.def_id());
self.check_codegen_attributes(callsite, callee_attrs)?;
self.check_mir_is_available(caller_body, &callsite.callee)?;
- let callee_body = self.tcx.instance_mir(callsite.callee.def);
+ let callee_body = try_instance_mir(self.tcx, callsite.callee.def)?;
self.check_mir_body(callsite, callee_body, callee_attrs)?;
if !self.tcx.consider_optimizing(|| {
@@ -1128,3 +1129,27 @@ impl<'tcx> MutVisitor<'tcx> for Integrator<'_, 'tcx> {
}
}
}
+
+#[instrument(skip(tcx), level = "debug")]
+fn try_instance_mir<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ instance: InstanceDef<'tcx>,
+) -> Result<&'tcx Body<'tcx>, &'static str> {
+ match instance {
+ ty::InstanceDef::DropGlue(_, Some(ty)) => match ty.kind() {
+ ty::Adt(def, substs) => {
+ let fields = def.all_fields();
+ for field in fields {
+ let field_ty = field.ty(tcx, substs);
+ if field_ty.has_param() && field_ty.has_projections() {
+ return Err("cannot build drop shim for polymorphic type");
+ }
+ }
+
+ Ok(tcx.instance_mir(instance))
+ }
+ _ => Ok(tcx.instance_mir(instance)),
+ },
+ _ => Ok(tcx.instance_mir(instance)),
+ }
+}
diff --git a/compiler/rustc_mir_transform/src/instsimplify.rs b/compiler/rustc_mir_transform/src/instsimplify.rs
index 6bff535586a..e4dc617620e 100644
--- a/compiler/rustc_mir_transform/src/instsimplify.rs
+++ b/compiler/rustc_mir_transform/src/instsimplify.rs
@@ -5,7 +5,6 @@ use crate::MirPass;
use rustc_hir::Mutability;
use rustc_middle::mir::*;
use rustc_middle::ty::layout::ValidityRequirement;
-use rustc_middle::ty::util::IntTypeExt;
use rustc_middle::ty::{self, ParamEnv, SubstsRef, Ty, TyCtxt};
use rustc_span::symbol::Symbol;
use rustc_target::abi::FieldIdx;
@@ -163,18 +162,6 @@ impl<'tcx> InstSimplifyContext<'tcx, '_> {
return;
}
- // Transmuting a fieldless enum to its repr is a discriminant read
- if let ty::Adt(adt_def, ..) = operand_ty.kind()
- && adt_def.is_enum()
- && adt_def.is_payloadfree()
- && let Some(place) = operand.place()
- && let Some(repr_int) = adt_def.repr().int
- && repr_int.to_ty(self.tcx) == *cast_ty
- {
- *rvalue = Rvalue::Discriminant(place);
- return;
- }
-
// Transmuting a transparent struct/union to a field's type is a projection
if let ty::Adt(adt_def, substs) = operand_ty.kind()
&& adt_def.repr().transparent()
diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs
index f25a9f042c4..65864dc016f 100644
--- a/compiler/rustc_mir_transform/src/lib.rs
+++ b/compiler/rustc_mir_transform/src/lib.rs
@@ -1,4 +1,6 @@
#![allow(rustc::potential_query_instability)]
+#![deny(rustc::untranslatable_diagnostic)]
+#![deny(rustc::diagnostic_outside_of_impl)]
#![feature(box_patterns)]
#![feature(drain_filter)]
#![feature(let_chains)]
@@ -32,7 +34,7 @@ use rustc_middle::mir::{
MirPhase, Operand, Place, ProjectionElem, Promoted, RuntimePhase, Rvalue, SourceInfo,
Statement, StatementKind, TerminatorKind, START_BLOCK,
};
-use rustc_middle::ty::query::Providers;
+use rustc_middle::query::Providers;
use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt};
use rustc_span::sym;
use rustc_trait_selection::traits;
@@ -69,6 +71,7 @@ pub mod dump_mir;
mod early_otherwise_branch;
mod elaborate_box_derefs;
mod elaborate_drops;
+mod errors;
mod ffi_unwind_calls;
mod function_item_references;
mod generator;
@@ -81,6 +84,7 @@ mod match_branches;
mod multiple_return_terminators;
mod normalize_array_len;
mod nrvo;
+mod ref_prop;
mod remove_noop_landing_pads;
mod remove_storage_markers;
mod remove_uninit_drops;
@@ -105,6 +109,11 @@ use rustc_const_eval::transform::promote_consts;
use rustc_const_eval::transform::validate;
use rustc_mir_dataflow::rustc_peek;
+use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage};
+use rustc_fluent_macro::fluent_messages;
+
+fluent_messages! { "../messages.ftl" }
+
pub fn provide(providers: &mut Providers) {
check_unsafety::provide(providers);
coverage::query::provide(providers);
@@ -551,6 +560,7 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
&separate_const_switch::SeparateConstSwitch,
&simplify::SimplifyLocals::BeforeConstProp,
&copy_prop::CopyProp,
+ &ref_prop::ReferencePropagation,
&const_prop::ConstProp,
&dataflow_const_prop::DataflowConstProp,
//
diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs
index 69ba4840146..dae01e41e5f 100644
--- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs
+++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs
@@ -1,6 +1,6 @@
//! Lowers intrinsic calls
-use crate::MirPass;
+use crate::{errors, MirPass};
use rustc_middle::mir::*;
use rustc_middle::ty::subst::SubstsRef;
use rustc_middle::ty::{self, Ty, TyCtxt};
@@ -310,11 +310,7 @@ fn resolve_rust_intrinsic<'tcx>(
}
fn validate_simd_shuffle<'tcx>(tcx: TyCtxt<'tcx>, args: &[Operand<'tcx>], span: Span) {
- match &args[2] {
- Operand::Constant(_) => {} // all good
- _ => {
- let msg = "last argument of `simd_shuffle` is required to be a `const` item";
- tcx.sess.span_err(span, msg);
- }
+ if !matches!(args[2], Operand::Constant(_)) {
+ tcx.sess.emit_err(errors::SimdShuffleLastConst { span });
}
}
diff --git a/compiler/rustc_mir_transform/src/normalize_array_len.rs b/compiler/rustc_mir_transform/src/normalize_array_len.rs
index b3b831bb4ab..3d61d33ce35 100644
--- a/compiler/rustc_mir_transform/src/normalize_array_len.rs
+++ b/compiler/rustc_mir_transform/src/normalize_array_len.rs
@@ -7,7 +7,6 @@ use rustc_index::IndexVec;
use rustc_middle::mir::visit::*;
use rustc_middle::mir::*;
use rustc_middle::ty::{self, TyCtxt};
-use rustc_mir_dataflow::impls::borrowed_locals;
pub struct NormalizeArrayLen;
@@ -24,9 +23,7 @@ impl<'tcx> MirPass<'tcx> for NormalizeArrayLen {
}
fn normalize_array_len_calls<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
- let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
- let borrowed_locals = borrowed_locals(body);
- let ssa = SsaLocals::new(tcx, param_env, body, &borrowed_locals);
+ let ssa = SsaLocals::new(body);
let slice_lengths = compute_slice_length(tcx, &ssa, body);
debug!(?slice_lengths);
@@ -41,7 +38,7 @@ fn compute_slice_length<'tcx>(
) -> IndexVec<Local, Option<ty::Const<'tcx>>> {
let mut slice_lengths = IndexVec::from_elem(None, &body.local_decls);
- for (local, rvalue) in ssa.assignments(body) {
+ for (local, rvalue, _) in ssa.assignments(body) {
match rvalue {
Rvalue::Cast(
CastKind::Pointer(ty::adjustment::PointerCast::Unsize),
diff --git a/compiler/rustc_mir_transform/src/nrvo.rs b/compiler/rustc_mir_transform/src/nrvo.rs
index b6e73eaad50..85b26220b1e 100644
--- a/compiler/rustc_mir_transform/src/nrvo.rs
+++ b/compiler/rustc_mir_transform/src/nrvo.rs
@@ -34,7 +34,8 @@ pub struct RenameReturnPlace;
impl<'tcx> MirPass<'tcx> for RenameReturnPlace {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
- sess.mir_opt_level() > 0
+ // #111005
+ sess.mir_opt_level() > 0 && sess.opts.unstable_opts.unsound_mir_opts
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>) {
diff --git a/compiler/rustc_mir_transform/src/ref_prop.rs b/compiler/rustc_mir_transform/src/ref_prop.rs
new file mode 100644
index 00000000000..bbd9f76ba5c
--- /dev/null
+++ b/compiler/rustc_mir_transform/src/ref_prop.rs
@@ -0,0 +1,408 @@
+use rustc_data_structures::fx::FxHashSet;
+use rustc_index::bit_set::BitSet;
+use rustc_index::IndexVec;
+use rustc_middle::mir::visit::*;
+use rustc_middle::mir::*;
+use rustc_middle::ty::TyCtxt;
+use rustc_mir_dataflow::impls::MaybeStorageDead;
+use rustc_mir_dataflow::storage::always_storage_live_locals;
+use rustc_mir_dataflow::Analysis;
+
+use crate::ssa::{SsaLocals, StorageLiveLocals};
+use crate::MirPass;
+
+/// Propagate references using SSA analysis.
+///
+/// MIR building may produce a lot of borrow-dereference patterns.
+///
+/// This pass aims to transform the following pattern:
+/// _1 = &raw? mut? PLACE;
+/// _3 = *_1;
+/// _4 = &raw? mut? *_1;
+///
+/// Into
+/// _1 = &raw? mut? PLACE;
+/// _3 = PLACE;
+/// _4 = &raw? mut? PLACE;
+///
+/// where `PLACE` is a direct or an indirect place expression.
+///
+/// There are 3 properties that need to be upheld for this transformation to be legal:
+/// - place stability: `PLACE` must refer to the same memory wherever it appears;
+/// - pointer liveness: we must not introduce dereferences of dangling pointers;
+/// - `&mut` borrow uniqueness.
+///
+/// # Stability
+///
+/// If `PLACE` is an indirect projection, if its of the form `(*LOCAL).PROJECTIONS` where:
+/// - `LOCAL` is SSA;
+/// - all projections in `PROJECTIONS` have a stable offset (no dereference and no indexing).
+///
+/// If `PLACE` is a direct projection of a local, we consider it as constant if:
+/// - the local is always live, or it has a single `StorageLive`;
+/// - all projections have a stable offset.
+///
+/// # Liveness
+///
+/// When performing a substitution, we must take care not to introduce uses of dangling locals.
+/// To ensure this, we walk the body with the `MaybeStorageDead` dataflow analysis:
+/// - if we want to replace `*x` by reborrow `*y` and `y` may be dead, we allow replacement and
+/// mark storage statements on `y` for removal;
+/// - if we want to replace `*x` by non-reborrow `y` and `y` must be live, we allow replacement;
+/// - if we want to replace `*x` by non-reborrow `y` and `y` may be dead, we do not replace.
+///
+/// # Uniqueness
+///
+/// For `&mut` borrows, we also need to preserve the uniqueness property:
+/// we must avoid creating a state where we interleave uses of `*_1` and `_2`.
+/// To do it, we only perform full substitution of mutable borrows:
+/// we replace either all or none of the occurrences of `*_1`.
+///
+/// Some care has to be taken when `_1` is copied in other locals.
+/// _1 = &raw? mut? _2;
+/// _3 = *_1;
+/// _4 = _1
+/// _5 = *_4
+/// In such cases, fully substituting `_1` means fully substituting all of the copies.
+///
+/// For immutable borrows, we do not need to preserve such uniqueness property,
+/// so we perform all the possible substitutions without removing the `_1 = &_2` statement.
+pub struct ReferencePropagation;
+
+impl<'tcx> MirPass<'tcx> for ReferencePropagation {
+ fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
+ sess.mir_opt_level() >= 4
+ }
+
+ #[instrument(level = "trace", skip(self, tcx, body))]
+ fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
+ debug!(def_id = ?body.source.def_id());
+ while propagate_ssa(tcx, body) {}
+ }
+}
+
+fn propagate_ssa<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool {
+ let ssa = SsaLocals::new(body);
+
+ let mut replacer = compute_replacement(tcx, body, &ssa);
+ debug!(?replacer.targets);
+ debug!(?replacer.allowed_replacements);
+ debug!(?replacer.storage_to_remove);
+
+ replacer.visit_body_preserves_cfg(body);
+
+ if replacer.any_replacement {
+ crate::simplify::remove_unused_definitions(body);
+ }
+
+ replacer.any_replacement
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum Value<'tcx> {
+ /// Not a pointer, or we can't know.
+ Unknown,
+ /// We know the value to be a pointer to this place.
+ /// The boolean indicates whether the reference is mutable, subject the uniqueness rule.
+ Pointer(Place<'tcx>, bool),
+}
+
+/// For each local, save the place corresponding to `*local`.
+#[instrument(level = "trace", skip(tcx, body))]
+fn compute_replacement<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ body: &Body<'tcx>,
+ ssa: &SsaLocals,
+) -> Replacer<'tcx> {
+ let always_live_locals = always_storage_live_locals(body);
+
+ // Compute which locals have a single `StorageLive` statement ever.
+ let storage_live = StorageLiveLocals::new(body, &always_live_locals);
+
+ // Compute `MaybeStorageDead` dataflow to check that we only replace when the pointee is
+ // definitely live.
+ let mut maybe_dead = MaybeStorageDead::new(always_live_locals)
+ .into_engine(tcx, body)
+ .iterate_to_fixpoint()
+ .into_results_cursor(body);
+
+ // Map for each local to the pointee.
+ let mut targets = IndexVec::from_elem(Value::Unknown, &body.local_decls);
+ // Set of locals for which we will remove their storage statement. This is useful for
+ // reborrowed references.
+ let mut storage_to_remove = BitSet::new_empty(body.local_decls.len());
+
+ let fully_replacable_locals = fully_replacable_locals(ssa);
+
+ // Returns true iff we can use `place` as a pointee.
+ //
+ // Note that we only need to verify that there is a single `StorageLive` statement, and we do
+ // not need to verify that it dominates all uses of that local.
+ //
+ // Consider the three statements:
+ // SL : StorageLive(a)
+ // DEF: b = &raw? mut? a
+ // USE: stuff that uses *b
+ //
+ // First, we recall that DEF is checked to dominate USE. Now imagine for the sake of
+ // contradiction there is a DEF -> SL -> USE path. Consider two cases:
+ //
+ // - DEF dominates SL. We always have UB the first time control flow reaches DEF,
+ // because the storage of `a` is dead. Since DEF dominates USE, that means we cannot
+ // reach USE and so our optimization is ok.
+ //
+ // - DEF does not dominate SL. Then there is a `START_BLOCK -> SL` path not including DEF.
+ // But we can extend this path to USE, meaning there is also a `START_BLOCK -> USE` path not
+ // including DEF. This violates the DEF dominates USE condition, and so is impossible.
+ let is_constant_place = |place: Place<'_>| {
+ // We only allow `Deref` as the first projection, to avoid surprises.
+ if place.projection.first() == Some(&PlaceElem::Deref) {
+ // `place == (*some_local).xxx`, it is constant only if `some_local` is constant.
+ // We approximate constness using SSAness.
+ ssa.is_ssa(place.local) && place.projection[1..].iter().all(PlaceElem::is_stable_offset)
+ } else {
+ storage_live.has_single_storage(place.local)
+ && place.projection[..].iter().all(PlaceElem::is_stable_offset)
+ }
+ };
+
+ let mut can_perform_opt = |target: Place<'tcx>, loc: Location| {
+ if target.projection.first() == Some(&PlaceElem::Deref) {
+ // We are creating a reborrow. As `place.local` is a reference, removing the storage
+ // statements should not make it much harder for LLVM to optimize.
+ storage_to_remove.insert(target.local);
+ true
+ } else {
+ // This is a proper dereference. We can only allow it if `target` is live.
+ maybe_dead.seek_after_primary_effect(loc);
+ let maybe_dead = maybe_dead.contains(target.local);
+ !maybe_dead
+ }
+ };
+
+ for (local, rvalue, location) in ssa.assignments(body) {
+ debug!(?local);
+
+ // Only visit if we have something to do.
+ let Value::Unknown = targets[local] else { bug!() };
+
+ let ty = body.local_decls[local].ty;
+
+ // If this is not a reference or pointer, do nothing.
+ if !ty.is_any_ptr() {
+ debug!("not a reference or pointer");
+ continue;
+ }
+
+ // Whether the current local is subject to the uniqueness rule.
+ let needs_unique = ty.is_mutable_ptr();
+
+ // If this a mutable reference that we cannot fully replace, mark it as unknown.
+ if needs_unique && !fully_replacable_locals.contains(local) {
+ debug!("not fully replaceable");
+ continue;
+ }
+
+ debug!(?rvalue);
+ match rvalue {
+ // This is a copy, just use the value we have in store for the previous one.
+ // As we are visiting in `assignment_order`, ie. reverse postorder, `rhs` should
+ // have been visited before.
+ Rvalue::Use(Operand::Copy(place) | Operand::Move(place))
+ | Rvalue::CopyForDeref(place) => {
+ if let Some(rhs) = place.as_local() && ssa.is_ssa(rhs) {
+ let target = targets[rhs];
+ // Only see through immutable reference and pointers, as we do not know yet if
+ // mutable references are fully replaced.
+ if !needs_unique && matches!(target, Value::Pointer(..)) {
+ targets[local] = target;
+ } else {
+ targets[local] = Value::Pointer(tcx.mk_place_deref(rhs.into()), needs_unique);
+ }
+ }
+ }
+ Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
+ let mut place = *place;
+ // Try to see through `place` in order to collapse reborrow chains.
+ if place.projection.first() == Some(&PlaceElem::Deref)
+ && let Value::Pointer(target, inner_needs_unique) = targets[place.local]
+ // Only see through immutable reference and pointers, as we do not know yet if
+ // mutable references are fully replaced.
+ && !inner_needs_unique
+ // Only collapse chain if the pointee is definitely live.
+ && can_perform_opt(target, location)
+ {
+ place = target.project_deeper(&place.projection[1..], tcx);
+ }
+ assert_ne!(place.local, local);
+ if is_constant_place(place) {
+ targets[local] = Value::Pointer(place, needs_unique);
+ }
+ }
+ // We do not know what to do, so keep as not-a-pointer.
+ _ => {}
+ }
+ }
+
+ debug!(?targets);
+
+ let mut finder = ReplacementFinder {
+ targets: &mut targets,
+ can_perform_opt,
+ allowed_replacements: FxHashSet::default(),
+ };
+ let reachable_blocks = traversal::reachable_as_bitset(body);
+ for (bb, bbdata) in body.basic_blocks.iter_enumerated() {
+ // Only visit reachable blocks as we rely on dataflow.
+ if reachable_blocks.contains(bb) {
+ finder.visit_basic_block_data(bb, bbdata);
+ }
+ }
+
+ let allowed_replacements = finder.allowed_replacements;
+ return Replacer {
+ tcx,
+ targets,
+ storage_to_remove,
+ allowed_replacements,
+ fully_replacable_locals,
+ any_replacement: false,
+ };
+
+ struct ReplacementFinder<'a, 'tcx, F> {
+ targets: &'a mut IndexVec<Local, Value<'tcx>>,
+ can_perform_opt: F,
+ allowed_replacements: FxHashSet<(Local, Location)>,
+ }
+
+ impl<'tcx, F> Visitor<'tcx> for ReplacementFinder<'_, 'tcx, F>
+ where
+ F: FnMut(Place<'tcx>, Location) -> bool,
+ {
+ fn visit_place(&mut self, place: &Place<'tcx>, ctxt: PlaceContext, loc: Location) {
+ if matches!(ctxt, PlaceContext::NonUse(_)) {
+ // There is no need to check liveness for non-uses.
+ return;
+ }
+
+ if place.projection.first() != Some(&PlaceElem::Deref) {
+ // This is not a dereference, nothing to do.
+ return;
+ }
+
+ let mut place = place.as_ref();
+ loop {
+ if let Value::Pointer(target, needs_unique) = self.targets[place.local] {
+ let perform_opt = (self.can_perform_opt)(target, loc);
+ debug!(?place, ?target, ?needs_unique, ?perform_opt);
+
+ // This a reborrow chain, recursively allow the replacement.
+ //
+ // This also allows to detect cases where `target.local` is not replacable,
+ // and mark it as such.
+ if let &[PlaceElem::Deref] = &target.projection[..] {
+ assert!(perform_opt);
+ self.allowed_replacements.insert((target.local, loc));
+ place.local = target.local;
+ continue;
+ } else if perform_opt {
+ self.allowed_replacements.insert((target.local, loc));
+ } else if needs_unique {
+ // This mutable reference is not fully replacable, so drop it.
+ self.targets[place.local] = Value::Unknown;
+ }
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+/// Compute the set of locals that can be fully replaced.
+///
+/// We consider a local to be replacable iff it's only used in a `Deref` projection `*_local` or
+/// non-use position (like storage statements and debuginfo).
+fn fully_replacable_locals(ssa: &SsaLocals) -> BitSet<Local> {
+ let mut replacable = BitSet::new_empty(ssa.num_locals());
+
+ // First pass: for each local, whether its uses can be fully replaced.
+ for local in ssa.locals() {
+ if ssa.num_direct_uses(local) == 0 {
+ replacable.insert(local);
+ }
+ }
+
+ // Second pass: a local can only be fully replaced if all its copies can.
+ ssa.meet_copy_equivalence(&mut replacable);
+
+ replacable
+}
+
+/// Utility to help performing subtitution of `*pattern` by `target`.
+struct Replacer<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ targets: IndexVec<Local, Value<'tcx>>,
+ storage_to_remove: BitSet<Local>,
+ allowed_replacements: FxHashSet<(Local, Location)>,
+ any_replacement: bool,
+ fully_replacable_locals: BitSet<Local>,
+}
+
+impl<'tcx> MutVisitor<'tcx> for Replacer<'tcx> {
+ fn tcx(&self) -> TyCtxt<'tcx> {
+ self.tcx
+ }
+
+ fn visit_var_debug_info(&mut self, debuginfo: &mut VarDebugInfo<'tcx>) {
+ if let VarDebugInfoContents::Place(ref mut place) = debuginfo.value
+ && place.projection.is_empty()
+ && let Value::Pointer(target, _) = self.targets[place.local]
+ && target.projection.iter().all(|p| p.can_use_in_debuginfo())
+ {
+ if let Some((&PlaceElem::Deref, rest)) = target.projection.split_last() {
+ *place = Place::from(target.local).project_deeper(rest, self.tcx);
+ self.any_replacement = true;
+ } else if self.fully_replacable_locals.contains(place.local)
+ && let Some(references) = debuginfo.references.checked_add(1)
+ {
+ debuginfo.references = references;
+ *place = target;
+ self.any_replacement = true;
+ }
+ }
+ }
+
+ fn visit_place(&mut self, place: &mut Place<'tcx>, ctxt: PlaceContext, loc: Location) {
+ if place.projection.first() != Some(&PlaceElem::Deref) {
+ return;
+ }
+
+ loop {
+ if let Value::Pointer(target, _) = self.targets[place.local] {
+ let perform_opt = matches!(ctxt, PlaceContext::NonUse(_))
+ || self.allowed_replacements.contains(&(target.local, loc));
+
+ if perform_opt {
+ *place = target.project_deeper(&place.projection[1..], self.tcx);
+ self.any_replacement = true;
+ continue;
+ }
+ }
+
+ break;
+ }
+ }
+
+ fn visit_statement(&mut self, stmt: &mut Statement<'tcx>, loc: Location) {
+ match stmt.kind {
+ StatementKind::StorageLive(l) | StatementKind::StorageDead(l)
+ if self.storage_to_remove.contains(l) =>
+ {
+ stmt.make_nop();
+ }
+ // Do not remove assignments as they may still be useful for debuginfo.
+ _ => self.super_statement(stmt, loc),
+ }
+ }
+}
diff --git a/compiler/rustc_mir_transform/src/shim.rs b/compiler/rustc_mir_transform/src/shim.rs
index 19d07fab0b9..7c47d8814db 100644
--- a/compiler/rustc_mir_transform/src/shim.rs
+++ b/compiler/rustc_mir_transform/src/shim.rs
@@ -2,7 +2,7 @@ use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_hir::lang_items::LangItem;
use rustc_middle::mir::*;
-use rustc_middle::ty::query::Providers;
+use rustc_middle::query::Providers;
use rustc_middle::ty::InternalSubsts;
use rustc_middle::ty::{self, EarlyBinder, GeneratorSubsts, Ty, TyCtxt};
use rustc_target::abi::{FieldIdx, VariantIdx, FIRST_VARIANT};
diff --git a/compiler/rustc_mir_transform/src/ssa.rs b/compiler/rustc_mir_transform/src/ssa.rs
index ec8d42c1652..2b404efccc7 100644
--- a/compiler/rustc_mir_transform/src/ssa.rs
+++ b/compiler/rustc_mir_transform/src/ssa.rs
@@ -1,3 +1,10 @@
+//! We denote as "SSA" the set of locals that verify the following properties:
+//! 1/ They are only assigned-to once, either as a function parameter, or in an assign statement;
+//! 2/ This single assignment dominates all uses;
+//!
+//! As a consequence of rule 2, we consider that borrowed locals are not SSA, even if they are
+//! `Freeze`, as we do not track that the assignment dominates all uses of the borrow.
+
use either::Either;
use rustc_data_structures::graph::dominators::Dominators;
use rustc_index::bit_set::BitSet;
@@ -5,7 +12,6 @@ use rustc_index::{IndexSlice, IndexVec};
use rustc_middle::middle::resolve_bound_vars::Set1;
use rustc_middle::mir::visit::*;
use rustc_middle::mir::*;
-use rustc_middle::ty::{ParamEnv, TyCtxt};
#[derive(Debug)]
pub struct SsaLocals {
@@ -17,6 +23,9 @@ pub struct SsaLocals {
assignment_order: Vec<Local>,
/// Copy equivalence classes between locals. See `copy_classes` for documentation.
copy_classes: IndexVec<Local, Local>,
+ /// Number of "direct" uses of each local, ie. uses that are not dereferences.
+ /// We ignore non-uses (Storage statements, debuginfo).
+ direct_uses: IndexVec<Local, u32>,
}
/// We often encounter MIR bodies with 1 or 2 basic blocks. In those cases, it's unnecessary to
@@ -26,48 +35,48 @@ struct SmallDominators {
inner: Option<Dominators<BasicBlock>>,
}
-trait DomExt {
- fn dominates(self, _other: Self, dominators: &SmallDominators) -> bool;
-}
-
-impl DomExt for Location {
- fn dominates(self, other: Location, dominators: &SmallDominators) -> bool {
- if self.block == other.block {
- self.statement_index <= other.statement_index
+impl SmallDominators {
+ fn dominates(&self, first: Location, second: Location) -> bool {
+ if first.block == second.block {
+ first.statement_index <= second.statement_index
+ } else if let Some(inner) = &self.inner {
+ inner.dominates(first.block, second.block)
} else {
- dominators.dominates(self.block, other.block)
+ first.block < second.block
}
}
-}
-impl SmallDominators {
- fn dominates(&self, dom: BasicBlock, node: BasicBlock) -> bool {
- if let Some(inner) = &self.inner { inner.dominates(dom, node) } else { dom < node }
+ fn check_dominates(&mut self, set: &mut Set1<LocationExtended>, loc: Location) {
+ let assign_dominates = match *set {
+ Set1::Empty | Set1::Many => false,
+ Set1::One(LocationExtended::Arg) => true,
+ Set1::One(LocationExtended::Plain(assign)) => {
+ self.dominates(assign.successor_within_block(), loc)
+ }
+ };
+ // We are visiting a use that is not dominated by an assignment.
+ // Either there is a cycle involved, or we are reading for uninitialized local.
+ // Bail out.
+ if !assign_dominates {
+ *set = Set1::Many;
+ }
}
}
impl SsaLocals {
- pub fn new<'tcx>(
- tcx: TyCtxt<'tcx>,
- param_env: ParamEnv<'tcx>,
- body: &Body<'tcx>,
- borrowed_locals: &BitSet<Local>,
- ) -> SsaLocals {
+ pub fn new<'tcx>(body: &Body<'tcx>) -> SsaLocals {
let assignment_order = Vec::with_capacity(body.local_decls.len());
let assignments = IndexVec::from_elem(Set1::Empty, &body.local_decls);
let dominators =
if body.basic_blocks.len() > 2 { Some(body.basic_blocks.dominators()) } else { None };
let dominators = SmallDominators { inner: dominators };
- let mut visitor = SsaVisitor { assignments, assignment_order, dominators };
- for (local, decl) in body.local_decls.iter_enumerated() {
- if matches!(body.local_kind(local), LocalKind::Arg) {
- visitor.assignments[local] = Set1::One(LocationExtended::Arg);
- }
- if borrowed_locals.contains(local) && !decl.ty.is_freeze(tcx, param_env) {
- visitor.assignments[local] = Set1::Many;
- }
+ let direct_uses = IndexVec::from_elem(0, &body.local_decls);
+ let mut visitor = SsaVisitor { assignments, assignment_order, dominators, direct_uses };
+
+ for local in body.args_iter() {
+ visitor.assignments[local] = Set1::One(LocationExtended::Arg);
}
if body.basic_blocks.len() > 2 {
@@ -85,36 +94,52 @@ impl SsaLocals {
}
debug!(?visitor.assignments);
+ debug!(?visitor.direct_uses);
visitor
.assignment_order
.retain(|&local| matches!(visitor.assignments[local], Set1::One(_)));
debug!(?visitor.assignment_order);
- let copy_classes = compute_copy_classes(&visitor, body);
-
- SsaLocals {
+ let mut ssa = SsaLocals {
assignments: visitor.assignments,
assignment_order: visitor.assignment_order,
- copy_classes,
- }
+ direct_uses: visitor.direct_uses,
+ // This is filled by `compute_copy_classes`.
+ copy_classes: IndexVec::default(),
+ };
+ compute_copy_classes(&mut ssa, body);
+ ssa
+ }
+
+ pub fn num_locals(&self) -> usize {
+ self.assignments.len()
+ }
+
+ pub fn locals(&self) -> impl Iterator<Item = Local> {
+ self.assignments.indices()
}
pub fn is_ssa(&self, local: Local) -> bool {
matches!(self.assignments[local], Set1::One(_))
}
+ /// Return the number of uses if a local that are not "Deref".
+ pub fn num_direct_uses(&self, local: Local) -> u32 {
+ self.direct_uses[local]
+ }
+
pub fn assignments<'a, 'tcx>(
&'a self,
body: &'a Body<'tcx>,
- ) -> impl Iterator<Item = (Local, &'a Rvalue<'tcx>)> + 'a {
+ ) -> impl Iterator<Item = (Local, &'a Rvalue<'tcx>, Location)> + 'a {
self.assignment_order.iter().filter_map(|&local| {
if let Set1::One(LocationExtended::Plain(loc)) = self.assignments[local] {
// `loc` must point to a direct assignment to `local`.
let Either::Left(stmt) = body.stmt_at(loc) else { bug!() };
let Some((target, rvalue)) = stmt.kind.as_assign() else { bug!() };
assert_eq!(target.as_local(), Some(local));
- Some((local, rvalue))
+ Some((local, rvalue, loc))
} else {
None
}
@@ -177,44 +202,29 @@ struct SsaVisitor {
dominators: SmallDominators,
assignments: IndexVec<Local, Set1<LocationExtended>>,
assignment_order: Vec<Local>,
-}
-
-impl SsaVisitor {
- fn check_assignment_dominates(&mut self, local: Local, loc: Location) {
- let set = &mut self.assignments[local];
- let assign_dominates = match *set {
- Set1::Empty | Set1::Many => false,
- Set1::One(LocationExtended::Arg) => true,
- Set1::One(LocationExtended::Plain(assign)) => {
- assign.successor_within_block().dominates(loc, &self.dominators)
- }
- };
- // We are visiting a use that is not dominated by an assignment.
- // Either there is a cycle involved, or we are reading for uninitialized local.
- // Bail out.
- if !assign_dominates {
- *set = Set1::Many;
- }
- }
+ direct_uses: IndexVec<Local, u32>,
}
impl<'tcx> Visitor<'tcx> for SsaVisitor {
fn visit_local(&mut self, local: Local, ctxt: PlaceContext, loc: Location) {
match ctxt {
- PlaceContext::MutatingUse(MutatingUseContext::Store) => {
- self.assignments[local].insert(LocationExtended::Plain(loc));
- if let Set1::One(_) = self.assignments[local] {
- // Only record if SSA-like, to avoid growing the vector needlessly.
- self.assignment_order.push(local);
- }
- }
+ PlaceContext::MutatingUse(MutatingUseContext::Projection)
+ | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => bug!(),
// Anything can happen with raw pointers, so remove them.
- PlaceContext::NonMutatingUse(NonMutatingUseContext::AddressOf)
- | PlaceContext::MutatingUse(_) => self.assignments[local] = Set1::Many,
- // Immutable borrows are taken into account in `SsaLocals::new` by
- // removing non-freeze locals.
+ // We do not verify that all uses of the borrow dominate the assignment to `local`,
+ // so we have to remove them too.
+ PlaceContext::NonMutatingUse(
+ NonMutatingUseContext::SharedBorrow
+ | NonMutatingUseContext::ShallowBorrow
+ | NonMutatingUseContext::UniqueBorrow
+ | NonMutatingUseContext::AddressOf,
+ )
+ | PlaceContext::MutatingUse(_) => {
+ self.assignments[local] = Set1::Many;
+ }
PlaceContext::NonMutatingUse(_) => {
- self.check_assignment_dominates(local, loc);
+ self.dominators.check_dominates(&mut self.assignments[local], loc);
+ self.direct_uses[local] += 1;
}
PlaceContext::NonUse(_) => {}
}
@@ -224,58 +234,113 @@ impl<'tcx> Visitor<'tcx> for SsaVisitor {
if place.projection.first() == Some(&PlaceElem::Deref) {
// Do not do anything for storage statements and debuginfo.
if ctxt.is_use() {
- // A use through a `deref` only reads from the local, and cannot write to it.
- let new_ctxt = PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection);
+ // Only change the context if it is a real use, not a "use" in debuginfo.
+ let new_ctxt = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy);
self.visit_projection(place.as_ref(), new_ctxt, loc);
- self.check_assignment_dominates(place.local, loc);
+ self.dominators.check_dominates(&mut self.assignments[place.local], loc);
}
return;
+ } else {
+ self.visit_projection(place.as_ref(), ctxt, loc);
+ self.visit_local(place.local, ctxt, loc);
}
- self.super_place(place, ctxt, loc);
+ }
+
+ fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, loc: Location) {
+ if let Some(local) = place.as_local() {
+ self.assignments[local].insert(LocationExtended::Plain(loc));
+ if let Set1::One(_) = self.assignments[local] {
+ // Only record if SSA-like, to avoid growing the vector needlessly.
+ self.assignment_order.push(local);
+ }
+ } else {
+ self.visit_place(place, PlaceContext::MutatingUse(MutatingUseContext::Store), loc);
+ }
+ self.visit_rvalue(rvalue, loc);
}
}
#[instrument(level = "trace", skip(ssa, body))]
-fn compute_copy_classes(ssa: &SsaVisitor, body: &Body<'_>) -> IndexVec<Local, Local> {
+fn compute_copy_classes(ssa: &mut SsaLocals, body: &Body<'_>) {
+ let mut direct_uses = std::mem::take(&mut ssa.direct_uses);
let mut copies = IndexVec::from_fn_n(|l| l, body.local_decls.len());
- for &local in &ssa.assignment_order {
- debug!(?local);
-
- if local == RETURN_PLACE {
- // `_0` is special, we cannot rename it.
- continue;
- }
-
- // This is not SSA: mark that we don't know the value.
- debug!(assignments = ?ssa.assignments[local]);
- let Set1::One(LocationExtended::Plain(loc)) = ssa.assignments[local] else { continue };
-
- // `loc` must point to a direct assignment to `local`.
- let Either::Left(stmt) = body.stmt_at(loc) else { bug!() };
- let Some((_target, rvalue)) = stmt.kind.as_assign() else { bug!() };
- assert_eq!(_target.as_local(), Some(local));
-
+ for (local, rvalue, _) in ssa.assignments(body) {
let (Rvalue::Use(Operand::Copy(place) | Operand::Move(place)) | Rvalue::CopyForDeref(place))
= rvalue
else { continue };
let Some(rhs) = place.as_local() else { continue };
- let Set1::One(_) = ssa.assignments[rhs] else { continue };
+ if !ssa.is_ssa(rhs) {
+ continue;
+ }
// We visit in `assignment_order`, ie. reverse post-order, so `rhs` has been
// visited before `local`, and we just have to copy the representing local.
- copies[local] = copies[rhs];
+ let head = copies[rhs];
+
+ if local == RETURN_PLACE {
+ // `_0` is special, we cannot rename it. Instead, rename the class of `rhs` to
+ // `RETURN_PLACE`. This is only possible if the class head is a temporary, not an
+ // argument.
+ if body.local_kind(head) != LocalKind::Temp {
+ continue;
+ }
+ for h in copies.iter_mut() {
+ if *h == head {
+ *h = RETURN_PLACE;
+ }
+ }
+ } else {
+ copies[local] = head;
+ }
+ direct_uses[rhs] -= 1;
}
debug!(?copies);
+ debug!(?direct_uses);
// Invariant: `copies` must point to the head of an equivalence class.
#[cfg(debug_assertions)]
for &head in copies.iter() {
assert_eq!(copies[head], head);
}
+ debug_assert_eq!(copies[RETURN_PLACE], RETURN_PLACE);
+
+ ssa.direct_uses = direct_uses;
+ ssa.copy_classes = copies;
+}
+
+#[derive(Debug)]
+pub(crate) struct StorageLiveLocals {
+ /// Set of "StorageLive" statements for each local.
+ storage_live: IndexVec<Local, Set1<LocationExtended>>,
+}
+
+impl StorageLiveLocals {
+ pub(crate) fn new(
+ body: &Body<'_>,
+ always_storage_live_locals: &BitSet<Local>,
+ ) -> StorageLiveLocals {
+ let mut storage_live = IndexVec::from_elem(Set1::Empty, &body.local_decls);
+ for local in always_storage_live_locals.iter() {
+ storage_live[local] = Set1::One(LocationExtended::Arg);
+ }
+ for (block, bbdata) in body.basic_blocks.iter_enumerated() {
+ for (statement_index, statement) in bbdata.statements.iter().enumerate() {
+ if let StatementKind::StorageLive(local) = statement.kind {
+ storage_live[local]
+ .insert(LocationExtended::Plain(Location { block, statement_index }));
+ }
+ }
+ }
+ debug!(?storage_live);
+ StorageLiveLocals { storage_live }
+ }
- copies
+ #[inline]
+ pub(crate) fn has_single_storage(&self, local: Local) -> bool {
+ matches!(self.storage_live[local], Set1::One(_))
+ }
}