Skip to content

Clarify Semantics of Assignment in MIR #97567

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add utilities to soundly handling overlapping statements.
  • Loading branch information
JakobDegen committed May 30, 2022
commit 51a19e13d72c90fb7af16b7565642e5fe698b310
52 changes: 52 additions & 0 deletions compiler/rustc_middle/src/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2170,6 +2170,58 @@ impl<'tcx> Place<'tcx> {

Place { local: self.local, projection: tcx.intern_place_elems(new_projections) }
}

/// If this returns true, then: If `self` and `other` are evaluated immediately in succession,
/// then the places that result from the evaluation are non-overlapping.
///
/// Gives no information if it returns false.
///
/// This places passed to this function must obey the derefered invariant, ie only the first
/// projection may be a deref projection.
pub fn is_disjoint(self, other: Place<'tcx>) -> bool {
use ProjectionElem::*;
let indirect_a = self.projection.get(0) == Some(&Deref);
let indirect_b = other.projection.get(0) == Some(&Deref);
if self.local != other.local {
// If the places are based on different locals, and neither of them is indirect, then
// they must each refer to memory inside their locals, which must be disjoint.
return !(indirect_a || indirect_b);
}

// We already know that the locals are the same. If both are indirect, then the result of
// dereferencing both must be the same. If neither are indirect, then the result of
// derefencing neither must be the same. If just one is indirect, we can't know. This also
// removes the deref projection, if it's there.
let (proj_a, proj_b) = match (indirect_a, indirect_b) {
(true, true) => (&self.projection[1..], &other.projection[1..]),
(false, false) => (&self.projection[..], &other.projection[..]),
_ => return false,
};

if let Some(pair) = std::iter::zip(proj_a, proj_b).find(|(a, b)| a != b) {
// Because we are interested in the places when they are evaluated immediately in
// succession, equal projections means equal places, even if there are indexing
// projections. Furthermore, there are no more derefs involved, since we removed those
// above.
match pair {
// The fields are different and different fields are disjoint
(Field(a, _), Field(b, _)) if a != b => true,
// The indexes are different, so the places are disjoint
(
ConstantIndex { offset: offset_a, from_end: from_end_a, .. },
ConstantIndex { offset: offset_b, from_end: from_end_b, .. },
) if offset_a != offset_b && from_end_a == from_end_b => true,
// The subranges are disjoint
(
Subslice { from: from_a, to: to_a, from_end: false },
Subslice { from: from_b, to: to_b, from_end: false },
) if to_a <= from_b || to_b <= from_a => true,
_ => false,
}
} else {
false
}
}
}

impl From<Local> for Place<'_> {
Expand Down
46 changes: 46 additions & 0 deletions compiler/rustc_middle/src/mir/tcx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,52 @@ impl<'tcx> Rvalue<'tcx> {
| Rvalue::ShallowInitBox(..) => false,
}
}

/// Ensures that an assignment statement obeys the rules for the left and right hand side being
/// non-overlapping.
///
/// This function accepts as input the sides of an assignment statement. It will then decide if
/// a temporary needs to be inserted, and if so insert that temporary by modifying the
/// assignment statement and returning a new statement to be inserted immediately after the
/// current one.
pub fn expand_assign(
stmt: &mut (Place<'tcx>, Rvalue<'tcx>),
source: SourceInfo,
locals: &mut LocalDecls<'tcx>,
tcx: TyCtxt<'tcx>,
) -> Option<Statement<'tcx>> {
let is_op_disjoint =
|op: &Operand<'tcx>| op.place().map(|p| p.is_disjoint(stmt.0)).unwrap_or(true);
let ok_as_is = match &stmt.1 {
Rvalue::Use(op) | Rvalue::Repeat(op, _) => is_op_disjoint(op),
Rvalue::Aggregate(_, ops) => ops.iter().all(|op| is_op_disjoint(op)),
Rvalue::Ref(..)
| Rvalue::ThreadLocalRef(..)
| Rvalue::AddressOf(..)
| Rvalue::Len(..)
| Rvalue::Cast(..)
| Rvalue::BinaryOp(..)
| Rvalue::CheckedBinaryOp(..)
| Rvalue::NullaryOp(..)
| Rvalue::UnaryOp(..)
| Rvalue::Discriminant(..)
| Rvalue::ShallowInitBox(..) => true,
};

if !ok_as_is {
// We need to introduce a temporary
let new_decl = LocalDecl::new(stmt.0.ty(locals, tcx).ty, source.span);
let temp: Place<'tcx> = locals.push(new_decl).into();
let new_statement = Statement {
source_info: source,
kind: StatementKind::Assign(Box::new((stmt.0, Rvalue::Use(Operand::Move(temp))))),
};
stmt.0 = temp;
Some(new_statement)
} else {
None
}
}
}

impl<'tcx> Operand<'tcx> {
Expand Down