Simplify code flow.
This commit is contained in:
parent
8356802862
commit
cae0dc2833
2 changed files with 157 additions and 222 deletions
|
@ -1,5 +1,6 @@
|
||||||
#![deny(rustc::untranslatable_diagnostic)]
|
#![deny(rustc::untranslatable_diagnostic)]
|
||||||
#![deny(rustc::diagnostic_outside_of_impl)]
|
#![deny(rustc::diagnostic_outside_of_impl)]
|
||||||
|
#![feature(assert_matches)]
|
||||||
#![feature(box_patterns)]
|
#![feature(box_patterns)]
|
||||||
#![feature(cow_is_borrowed)]
|
#![feature(cow_is_borrowed)]
|
||||||
#![feature(decl_macro)]
|
#![feature(decl_macro)]
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
//! initialization and can otherwise silence errors, if
|
//! initialization and can otherwise silence errors, if
|
||||||
//! move analysis runs after promotion on broken MIR.
|
//! move analysis runs after promotion on broken MIR.
|
||||||
|
|
||||||
|
use either::{Left, Right};
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_middle::mir;
|
use rustc_middle::mir;
|
||||||
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
|
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
|
||||||
|
@ -22,6 +23,7 @@ use rustc_span::Span;
|
||||||
|
|
||||||
use rustc_index::{Idx, IndexSlice, IndexVec};
|
use rustc_index::{Idx, IndexSlice, IndexVec};
|
||||||
|
|
||||||
|
use std::assert_matches::assert_matches;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::{cmp, iter, mem};
|
use std::{cmp, iter, mem};
|
||||||
|
|
||||||
|
@ -116,41 +118,38 @@ impl<'tcx> Visitor<'tcx> for Collector<'_, 'tcx> {
|
||||||
|
|
||||||
let temp = &mut self.temps[index];
|
let temp = &mut self.temps[index];
|
||||||
debug!("visit_local: temp={:?}", temp);
|
debug!("visit_local: temp={:?}", temp);
|
||||||
if *temp == TempState::Undefined {
|
*temp = match *temp {
|
||||||
match context {
|
TempState::Undefined => match context {
|
||||||
PlaceContext::MutatingUse(MutatingUseContext::Store)
|
PlaceContext::MutatingUse(MutatingUseContext::Store)
|
||||||
| PlaceContext::MutatingUse(MutatingUseContext::Call) => {
|
| PlaceContext::MutatingUse(MutatingUseContext::Call) => {
|
||||||
*temp = TempState::Defined { location, uses: 0, valid: Err(()) };
|
TempState::Defined { location, uses: 0, valid: Err(()) }
|
||||||
|
}
|
||||||
|
_ => TempState::Unpromotable,
|
||||||
|
},
|
||||||
|
TempState::Defined { ref mut uses, .. } => {
|
||||||
|
// We always allow borrows, even mutable ones, as we need
|
||||||
|
// to promote mutable borrows of some ZSTs e.g., `&mut []`.
|
||||||
|
let allowed_use = match context {
|
||||||
|
PlaceContext::MutatingUse(MutatingUseContext::Borrow)
|
||||||
|
| PlaceContext::NonMutatingUse(_) => true,
|
||||||
|
PlaceContext::MutatingUse(_) | PlaceContext::NonUse(_) => false,
|
||||||
|
};
|
||||||
|
debug!("visit_local: allowed_use={:?}", allowed_use);
|
||||||
|
if allowed_use {
|
||||||
|
*uses += 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_ => { /* mark as unpromotable below */ }
|
TempState::Unpromotable
|
||||||
}
|
}
|
||||||
} else if let TempState::Defined { uses, .. } = temp {
|
_ => TempState::Unpromotable,
|
||||||
// We always allow borrows, even mutable ones, as we need
|
};
|
||||||
// to promote mutable borrows of some ZSTs e.g., `&mut []`.
|
|
||||||
let allowed_use = match context {
|
|
||||||
PlaceContext::MutatingUse(MutatingUseContext::Borrow)
|
|
||||||
| PlaceContext::NonMutatingUse(_) => true,
|
|
||||||
PlaceContext::MutatingUse(_) | PlaceContext::NonUse(_) => false,
|
|
||||||
};
|
|
||||||
debug!("visit_local: allowed_use={:?}", allowed_use);
|
|
||||||
if allowed_use {
|
|
||||||
*uses += 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* mark as unpromotable below */
|
|
||||||
}
|
|
||||||
*temp = TempState::Unpromotable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
|
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
|
||||||
self.super_rvalue(rvalue, location);
|
self.super_rvalue(rvalue, location);
|
||||||
|
|
||||||
match *rvalue {
|
if let Rvalue::Ref(..) = *rvalue {
|
||||||
Rvalue::Ref(..) => {
|
self.candidates.push(Candidate { location });
|
||||||
self.candidates.push(Candidate { location });
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,230 +188,165 @@ struct Unpromotable;
|
||||||
|
|
||||||
impl<'tcx> Validator<'_, 'tcx> {
|
impl<'tcx> Validator<'_, 'tcx> {
|
||||||
fn validate_candidate(&mut self, candidate: Candidate) -> Result<(), Unpromotable> {
|
fn validate_candidate(&mut self, candidate: Candidate) -> Result<(), Unpromotable> {
|
||||||
let loc = candidate.location;
|
let Left(statement) = self.body.stmt_at(candidate.location) else { bug!() };
|
||||||
let statement = &self.body[loc.block].statements[loc.statement_index];
|
let Some((_, Rvalue::Ref(_, kind, place))) = statement.kind.as_assign() else { bug!() };
|
||||||
match &statement.kind {
|
|
||||||
StatementKind::Assign(box (_, Rvalue::Ref(_, kind, place))) => {
|
|
||||||
// We can only promote interior borrows of promotable temps (non-temps
|
|
||||||
// don't get promoted anyway).
|
|
||||||
self.validate_local(place.local)?;
|
|
||||||
|
|
||||||
// The reference operation itself must be promotable.
|
// We can only promote interior borrows of promotable temps (non-temps
|
||||||
// (Needs to come after `validate_local` to avoid ICEs.)
|
// don't get promoted anyway).
|
||||||
self.validate_ref(*kind, place)?;
|
self.validate_local(place.local)?;
|
||||||
|
|
||||||
// We do not check all the projections (they do not get promoted anyway),
|
// The reference operation itself must be promotable.
|
||||||
// but we do stay away from promoting anything involving a dereference.
|
// (Needs to come after `validate_local` to avoid ICEs.)
|
||||||
if place.projection.contains(&ProjectionElem::Deref) {
|
self.validate_ref(*kind, place)?;
|
||||||
return Err(Unpromotable);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
// We do not check all the projections (they do not get promoted anyway),
|
||||||
}
|
// but we do stay away from promoting anything involving a dereference.
|
||||||
_ => bug!(),
|
if place.projection.contains(&ProjectionElem::Deref) {
|
||||||
|
return Err(Unpromotable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME(eddyb) maybe cache this?
|
// FIXME(eddyb) maybe cache this?
|
||||||
fn qualif_local<Q: qualifs::Qualif>(&mut self, local: Local) -> bool {
|
fn qualif_local<Q: qualifs::Qualif>(&mut self, local: Local) -> bool {
|
||||||
if let TempState::Defined { location: loc, .. } = self.temps[local] {
|
let TempState::Defined { location: loc, .. } = self.temps[local] else {
|
||||||
let num_stmts = self.body[loc.block].statements.len();
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
if loc.statement_index < num_stmts {
|
let stmt_or_term = self.body.stmt_at(loc);
|
||||||
let statement = &self.body[loc.block].statements[loc.statement_index];
|
match stmt_or_term {
|
||||||
match &statement.kind {
|
Left(statement) => {
|
||||||
StatementKind::Assign(box (_, rhs)) => qualifs::in_rvalue::<Q, _>(
|
let Some((_, rhs)) = statement.kind.as_assign() else {
|
||||||
self.ccx,
|
span_bug!(statement.source_info.span, "{:?} is not an assignment", statement)
|
||||||
&mut |l| self.qualif_local::<Q>(l),
|
};
|
||||||
rhs,
|
qualifs::in_rvalue::<Q, _>(self.ccx, &mut |l| self.qualif_local::<Q>(l), rhs)
|
||||||
),
|
}
|
||||||
_ => {
|
Right(terminator) => {
|
||||||
span_bug!(
|
assert_matches!(terminator.kind, TerminatorKind::Call { .. });
|
||||||
statement.source_info.span,
|
let return_ty = self.body.local_decls[local].ty;
|
||||||
"{:?} is not an assignment",
|
Q::in_any_value_of_ty(self.ccx, return_ty)
|
||||||
statement
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let terminator = self.body[loc.block].terminator();
|
|
||||||
match &terminator.kind {
|
|
||||||
TerminatorKind::Call { .. } => {
|
|
||||||
let return_ty = self.body.local_decls[local].ty;
|
|
||||||
Q::in_any_value_of_ty(self.ccx, return_ty)
|
|
||||||
}
|
|
||||||
kind => {
|
|
||||||
span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_local(&mut self, local: Local) -> Result<(), Unpromotable> {
|
fn validate_local(&mut self, local: Local) -> Result<(), Unpromotable> {
|
||||||
if let TempState::Defined { location: loc, uses, valid } = self.temps[local] {
|
let TempState::Defined { location: loc, uses, valid } = self.temps[local] else {
|
||||||
// We cannot promote things that need dropping, since the promoted value
|
return Err(Unpromotable);
|
||||||
// would not get dropped.
|
};
|
||||||
if self.qualif_local::<qualifs::NeedsDrop>(local) {
|
|
||||||
return Err(Unpromotable);
|
|
||||||
}
|
|
||||||
valid.or_else(|_| {
|
|
||||||
let ok = {
|
|
||||||
let block = &self.body[loc.block];
|
|
||||||
let num_stmts = block.statements.len();
|
|
||||||
|
|
||||||
if loc.statement_index < num_stmts {
|
// We cannot promote things that need dropping, since the promoted value would not get
|
||||||
let statement = &block.statements[loc.statement_index];
|
// dropped.
|
||||||
match &statement.kind {
|
if self.qualif_local::<qualifs::NeedsDrop>(local) {
|
||||||
StatementKind::Assign(box (_, rhs)) => self.validate_rvalue(rhs),
|
return Err(Unpromotable);
|
||||||
_ => {
|
|
||||||
span_bug!(
|
|
||||||
statement.source_info.span,
|
|
||||||
"{:?} is not an assignment",
|
|
||||||
statement
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let terminator = block.terminator();
|
|
||||||
match &terminator.kind {
|
|
||||||
TerminatorKind::Call { func, args, .. } => {
|
|
||||||
self.validate_call(func, args)
|
|
||||||
}
|
|
||||||
TerminatorKind::Yield { .. } => Err(Unpromotable),
|
|
||||||
kind => {
|
|
||||||
span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.temps[local] = match ok {
|
|
||||||
Ok(()) => TempState::Defined { location: loc, uses, valid: Ok(()) },
|
|
||||||
Err(_) => TempState::Unpromotable,
|
|
||||||
};
|
|
||||||
ok
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(Unpromotable)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if valid.is_ok() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let ok = {
|
||||||
|
let stmt_or_term = self.body.stmt_at(loc);
|
||||||
|
match stmt_or_term {
|
||||||
|
Left(statement) => {
|
||||||
|
let Some((_, rhs)) = statement.kind.as_assign() else {
|
||||||
|
span_bug!(
|
||||||
|
statement.source_info.span,
|
||||||
|
"{:?} is not an assignment",
|
||||||
|
statement
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.validate_rvalue(rhs)
|
||||||
|
}
|
||||||
|
Right(terminator) => match &terminator.kind {
|
||||||
|
TerminatorKind::Call { func, args, .. } => self.validate_call(func, args),
|
||||||
|
TerminatorKind::Yield { .. } => Err(Unpromotable),
|
||||||
|
kind => {
|
||||||
|
span_bug!(terminator.source_info.span, "{:?} not promotable", kind);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.temps[local] = match ok {
|
||||||
|
Ok(()) => TempState::Defined { location: loc, uses, valid: Ok(()) },
|
||||||
|
Err(_) => TempState::Unpromotable,
|
||||||
|
};
|
||||||
|
|
||||||
|
ok
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_place(&mut self, place: PlaceRef<'tcx>) -> Result<(), Unpromotable> {
|
fn validate_place(&mut self, place: PlaceRef<'tcx>) -> Result<(), Unpromotable> {
|
||||||
match place.last_projection() {
|
let Some((place_base, elem)) = place.last_projection() else {
|
||||||
None => self.validate_local(place.local),
|
return self.validate_local(place.local);
|
||||||
Some((place_base, elem)) => {
|
};
|
||||||
// Validate topmost projection, then recurse.
|
|
||||||
match elem {
|
|
||||||
ProjectionElem::Deref => {
|
|
||||||
let mut promotable = false;
|
|
||||||
// When a static is used by-value, that gets desugared to `*STATIC_ADDR`,
|
|
||||||
// and we need to be able to promote this. So check if this deref matches
|
|
||||||
// that specific pattern.
|
|
||||||
|
|
||||||
// We need to make sure this is a `Deref` of a local with no further projections.
|
// Validate topmost projection, then recurse.
|
||||||
// Discussion can be found at
|
match elem {
|
||||||
// https://github.com/rust-lang/rust/pull/74945#discussion_r463063247
|
// Recurse directly.
|
||||||
if let Some(local) = place_base.as_local() {
|
ProjectionElem::ConstantIndex { .. }
|
||||||
if let TempState::Defined { location, .. } = self.temps[local] {
|
| ProjectionElem::Subtype(_)
|
||||||
let def_stmt = self.body[location.block]
|
| ProjectionElem::Subslice { .. } => {}
|
||||||
.statements
|
|
||||||
.get(location.statement_index);
|
|
||||||
if let Some(Statement {
|
|
||||||
kind:
|
|
||||||
StatementKind::Assign(box (
|
|
||||||
_,
|
|
||||||
Rvalue::Use(Operand::Constant(c)),
|
|
||||||
)),
|
|
||||||
..
|
|
||||||
}) = def_stmt
|
|
||||||
{
|
|
||||||
if let Some(did) = c.check_static_ptr(self.tcx) {
|
|
||||||
// Evaluating a promoted may not read statics except if it got
|
|
||||||
// promoted from a static (this is a CTFE check). So we
|
|
||||||
// can only promote static accesses inside statics.
|
|
||||||
if let Some(hir::ConstContext::Static(..)) = self.const_kind
|
|
||||||
{
|
|
||||||
if !self.tcx.is_thread_local_static(did) {
|
|
||||||
promotable = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !promotable {
|
|
||||||
return Err(Unpromotable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ProjectionElem::OpaqueCast(..) | ProjectionElem::Downcast(..) => {
|
|
||||||
return Err(Unpromotable);
|
|
||||||
}
|
|
||||||
|
|
||||||
ProjectionElem::ConstantIndex { .. }
|
// Never recurse.
|
||||||
| ProjectionElem::Subtype(_)
|
ProjectionElem::OpaqueCast(..) | ProjectionElem::Downcast(..) => {
|
||||||
| ProjectionElem::Subslice { .. } => {}
|
return Err(Unpromotable);
|
||||||
|
}
|
||||||
|
|
||||||
ProjectionElem::Index(local) => {
|
ProjectionElem::Deref => {
|
||||||
let mut promotable = false;
|
// When a static is used by-value, that gets desugared to `*STATIC_ADDR`,
|
||||||
// Only accept if we can predict the index and are indexing an array.
|
// and we need to be able to promote this. So check if this deref matches
|
||||||
let val = if let TempState::Defined { location: loc, .. } =
|
// that specific pattern.
|
||||||
self.temps[local]
|
|
||||||
{
|
|
||||||
let block = &self.body[loc.block];
|
|
||||||
if loc.statement_index < block.statements.len() {
|
|
||||||
let statement = &block.statements[loc.statement_index];
|
|
||||||
match &statement.kind {
|
|
||||||
StatementKind::Assign(box (
|
|
||||||
_,
|
|
||||||
Rvalue::Use(Operand::Constant(c)),
|
|
||||||
)) => c.const_.try_eval_target_usize(self.tcx, self.param_env),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
if let Some(idx) = val {
|
|
||||||
// Determine the type of the thing we are indexing.
|
|
||||||
let ty = place_base.ty(self.body, self.tcx).ty;
|
|
||||||
match ty.kind() {
|
|
||||||
ty::Array(_, len) => {
|
|
||||||
// It's an array; determine its length.
|
|
||||||
if let Some(len) =
|
|
||||||
len.try_eval_target_usize(self.tcx, self.param_env)
|
|
||||||
{
|
|
||||||
// If the index is in-bounds, go ahead.
|
|
||||||
if idx < len {
|
|
||||||
promotable = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !promotable {
|
|
||||||
return Err(Unpromotable);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.validate_local(local)?;
|
// We need to make sure this is a `Deref` of a local with no further projections.
|
||||||
}
|
// Discussion can be found at
|
||||||
|
// https://github.com/rust-lang/rust/pull/74945#discussion_r463063247
|
||||||
ProjectionElem::Field(..) => {
|
if let Some(local) = place_base.as_local()
|
||||||
let base_ty = place_base.ty(self.body, self.tcx).ty;
|
&& let TempState::Defined { location, .. } = self.temps[local]
|
||||||
if base_ty.is_union() {
|
&& let Left(def_stmt) = self.body.stmt_at(location)
|
||||||
// No promotion of union field accesses.
|
&& let Some((_, Rvalue::Use(Operand::Constant(c)))) = def_stmt.kind.as_assign()
|
||||||
return Err(Unpromotable);
|
&& let Some(did) = c.check_static_ptr(self.tcx)
|
||||||
}
|
// Evaluating a promoted may not read statics except if it got
|
||||||
}
|
// promoted from a static (this is a CTFE check). So we
|
||||||
|
// can only promote static accesses inside statics.
|
||||||
|
&& let Some(hir::ConstContext::Static(..)) = self.const_kind
|
||||||
|
&& !self.tcx.is_thread_local_static(did)
|
||||||
|
{
|
||||||
|
// Recurse.
|
||||||
|
} else {
|
||||||
|
return Err(Unpromotable);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
ProjectionElem::Index(local) => {
|
||||||
|
// Only accept if we can predict the index and are indexing an array.
|
||||||
|
if let TempState::Defined { location: loc, .. } = self.temps[local]
|
||||||
|
&& let Left(statement) = self.body.stmt_at(loc)
|
||||||
|
&& let Some((_, Rvalue::Use(Operand::Constant(c)))) = statement.kind.as_assign()
|
||||||
|
&& let Some(idx) = c.const_.try_eval_target_usize(self.tcx, self.param_env)
|
||||||
|
// Determine the type of the thing we are indexing.
|
||||||
|
&& let ty::Array(_, len) = place_base.ty(self.body, self.tcx).ty.kind()
|
||||||
|
// It's an array; determine its length.
|
||||||
|
&& let Some(len) = len.try_eval_target_usize(self.tcx, self.param_env)
|
||||||
|
// If the index is in-bounds, go ahead.
|
||||||
|
&& idx < len
|
||||||
|
{
|
||||||
|
self.validate_local(local)?;
|
||||||
|
// Recurse.
|
||||||
|
} else {
|
||||||
|
return Err(Unpromotable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.validate_place(place_base)
|
ProjectionElem::Field(..) => {
|
||||||
|
let base_ty = place_base.ty(self.body, self.tcx).ty;
|
||||||
|
if base_ty.is_union() {
|
||||||
|
// No promotion of union field accesses.
|
||||||
|
return Err(Unpromotable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.validate_place(place_base)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_operand(&mut self, operand: &Operand<'tcx>) -> Result<(), Unpromotable> {
|
fn validate_operand(&mut self, operand: &Operand<'tcx>) -> Result<(), Unpromotable> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue