Skip to content

Commit 1db5c6b

Browse files
authored
Rollup merge of rust-lang#95320 - JakobDegen:mir-docs, r=oli-obk
Document the current MIR semantics that are clear from existing code This PR adds documentation to places, operands, rvalues, statementkinds, and terminatorkinds that describes their existing semantics and requirements. In many places the semantics depend on the Rust memory model or other T-Lang decisions - when this is the case, it is just noted as such with links to UCG issues where possible. I'm hopeful that none of the documentation added here can be used to justify optimizations that depend on the memory model. The documentation for places and operands probably comes closest to running afoul of this - if people think that it cannot be merged as is, it can definitely also be taken out. The goal here is to only document parts of MIR that seem to be decided already, or are at least depended on by existing code. That leaves quite a number of open questions - those are marked as "needs clarification." I'm not sure what to do with those in this PR - we obviously can't decide all these questions here. Should I just leave them in as is? Take them out? Keep them in but as `//` instead of `///` comments? If this is too big to review at once, I can split this up. r? rust-lang/mir-opt
2 parents de392c7 + 8732bf5 commit 1db5c6b

File tree

7 files changed

+633
-145
lines changed

7 files changed

+633
-145
lines changed

compiler/rustc_const_eval/src/transform/validate.rs

Lines changed: 185 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
use rustc_index::bit_set::BitSet;
44
use rustc_infer::infer::TyCtxtInferExt;
55
use rustc_middle::mir::interpret::Scalar;
6-
use rustc_middle::mir::traversal;
76
use rustc_middle::mir::visit::{PlaceContext, Visitor};
87
use rustc_middle::mir::{
9-
AggregateKind, BasicBlock, Body, BorrowKind, Local, Location, MirPass, MirPhase, Operand,
10-
PlaceElem, PlaceRef, ProjectionElem, Rvalue, SourceScope, Statement, StatementKind, Terminator,
11-
TerminatorKind, START_BLOCK,
8+
traversal, AggregateKind, BasicBlock, BinOp, Body, BorrowKind, Local, Location, MirPass,
9+
MirPhase, Operand, Place, PlaceElem, PlaceRef, ProjectionElem, Rvalue, SourceScope, Statement,
10+
StatementKind, Terminator, TerminatorKind, UnOp, START_BLOCK,
1211
};
1312
use rustc_middle::ty::fold::BottomUpFolder;
14-
use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt, TypeFoldable};
13+
use rustc_middle::ty::{self, InstanceDef, ParamEnv, Ty, TyCtxt, TypeFoldable};
1514
use rustc_mir_dataflow::impls::MaybeStorageLive;
1615
use rustc_mir_dataflow::storage::AlwaysLiveLocals;
1716
use rustc_mir_dataflow::{Analysis, ResultsCursor};
@@ -36,6 +35,13 @@ pub struct Validator {
3635

3736
impl<'tcx> MirPass<'tcx> for Validator {
3837
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
38+
// FIXME(JakobDegen): These bodies never instantiated in codegend anyway, so it's not
39+
// terribly important that they pass the validator. However, I think other passes might
40+
// still see them, in which case they might be surprised. It would probably be better if we
41+
// didn't put this through the MIR pipeline at all.
42+
if matches!(body.source.instance, InstanceDef::Intrinsic(..) | InstanceDef::Virtual(..)) {
43+
return;
44+
}
3945
let def_id = body.source.def_id();
4046
let param_env = tcx.param_env(def_id);
4147
let mir_phase = self.mir_phase;
@@ -240,58 +246,179 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
240246
self.super_projection_elem(local, proj_base, elem, context, location);
241247
}
242248

243-
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
244-
match &statement.kind {
245-
StatementKind::Assign(box (dest, rvalue)) => {
246-
// LHS and RHS of the assignment must have the same type.
247-
let left_ty = dest.ty(&self.body.local_decls, self.tcx).ty;
248-
let right_ty = rvalue.ty(&self.body.local_decls, self.tcx);
249-
if !self.mir_assign_valid_types(right_ty, left_ty) {
249+
fn visit_place(&mut self, place: &Place<'tcx>, _: PlaceContext, _: Location) {
250+
// Set off any `bug!`s in the type computation code
251+
let _ = place.ty(&self.body.local_decls, self.tcx);
252+
}
253+
254+
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
255+
macro_rules! check_kinds {
256+
($t:expr, $text:literal, $($patterns:tt)*) => {
257+
if !matches!(($t).kind(), $($patterns)*) {
258+
self.fail(location, format!($text, $t));
259+
}
260+
};
261+
}
262+
match rvalue {
263+
Rvalue::Use(_) => {}
264+
Rvalue::Aggregate(agg_kind, _) => {
265+
let disallowed = match **agg_kind {
266+
AggregateKind::Array(..) => false,
267+
AggregateKind::Generator(..) => self.mir_phase >= MirPhase::GeneratorsLowered,
268+
_ => self.mir_phase >= MirPhase::Deaggregated,
269+
};
270+
if disallowed {
250271
self.fail(
251272
location,
252-
format!(
253-
"encountered `{:?}` with incompatible types:\n\
254-
left-hand side has type: {}\n\
255-
right-hand side has type: {}",
256-
statement.kind, left_ty, right_ty,
257-
),
273+
format!("{:?} have been lowered to field assignments", rvalue),
274+
)
275+
}
276+
}
277+
Rvalue::Ref(_, BorrowKind::Shallow, _) => {
278+
if self.mir_phase >= MirPhase::DropsLowered {
279+
self.fail(
280+
location,
281+
"`Assign` statement with a `Shallow` borrow should have been removed after drop lowering phase",
258282
);
259283
}
260-
match rvalue {
261-
// The sides of an assignment must not alias. Currently this just checks whether the places
262-
// are identical.
263-
Rvalue::Use(Operand::Copy(src) | Operand::Move(src)) => {
264-
if dest == src {
284+
}
285+
Rvalue::Len(p) => {
286+
let pty = p.ty(&self.body.local_decls, self.tcx).ty;
287+
check_kinds!(
288+
pty,
289+
"Cannot compute length of non-array type {:?}",
290+
ty::Array(..) | ty::Slice(..)
291+
);
292+
}
293+
Rvalue::BinaryOp(op, vals) | Rvalue::CheckedBinaryOp(op, vals) => {
294+
use BinOp::*;
295+
let a = vals.0.ty(&self.body.local_decls, self.tcx);
296+
let b = vals.1.ty(&self.body.local_decls, self.tcx);
297+
match op {
298+
Offset => {
299+
check_kinds!(a, "Cannot offset non-pointer type {:?}", ty::RawPtr(..));
300+
if b != self.tcx.types.isize && b != self.tcx.types.usize {
301+
self.fail(location, format!("Cannot offset by non-isize type {:?}", b));
302+
}
303+
}
304+
Eq | Lt | Le | Ne | Ge | Gt => {
305+
for x in [a, b] {
306+
check_kinds!(
307+
x,
308+
"Cannot compare type {:?}",
309+
ty::Bool
310+
| ty::Char
311+
| ty::Int(..)
312+
| ty::Uint(..)
313+
| ty::Float(..)
314+
| ty::RawPtr(..)
315+
| ty::FnPtr(..)
316+
)
317+
}
318+
// None of the possible types have lifetimes, so we can just compare
319+
// directly
320+
if a != b {
265321
self.fail(
266322
location,
267-
"encountered `Assign` statement with overlapping memory",
323+
format!("Cannot compare unequal types {:?} and {:?}", a, b),
268324
);
269325
}
270326
}
271-
Rvalue::Aggregate(agg_kind, _) => {
272-
let disallowed = match **agg_kind {
273-
AggregateKind::Array(..) => false,
274-
AggregateKind::Generator(..) => {
275-
self.mir_phase >= MirPhase::GeneratorsLowered
276-
}
277-
_ => self.mir_phase >= MirPhase::Deaggregated,
278-
};
279-
if disallowed {
327+
Shl | Shr => {
328+
for x in [a, b] {
329+
check_kinds!(
330+
x,
331+
"Cannot shift non-integer type {:?}",
332+
ty::Uint(..) | ty::Int(..)
333+
)
334+
}
335+
}
336+
BitAnd | BitOr | BitXor => {
337+
for x in [a, b] {
338+
check_kinds!(
339+
x,
340+
"Cannot perform bitwise op on type {:?}",
341+
ty::Uint(..) | ty::Int(..) | ty::Bool
342+
)
343+
}
344+
if a != b {
280345
self.fail(
281346
location,
282-
format!("{:?} have been lowered to field assignments", rvalue),
283-
)
347+
format!(
348+
"Cannot perform bitwise op on unequal types {:?} and {:?}",
349+
a, b
350+
),
351+
);
284352
}
285353
}
286-
Rvalue::Ref(_, BorrowKind::Shallow, _) => {
287-
if self.mir_phase >= MirPhase::DropsLowered {
354+
Add | Sub | Mul | Div | Rem => {
355+
for x in [a, b] {
356+
check_kinds!(
357+
x,
358+
"Cannot perform op on type {:?}",
359+
ty::Uint(..) | ty::Int(..) | ty::Float(..)
360+
)
361+
}
362+
if a != b {
288363
self.fail(
289364
location,
290-
"`Assign` statement with a `Shallow` borrow should have been removed after drop lowering phase",
365+
format!("Cannot perform op on unequal types {:?} and {:?}", a, b),
291366
);
292367
}
293368
}
294-
_ => {}
369+
}
370+
}
371+
Rvalue::UnaryOp(op, operand) => {
372+
let a = operand.ty(&self.body.local_decls, self.tcx);
373+
match op {
374+
UnOp::Neg => {
375+
check_kinds!(a, "Cannot negate type {:?}", ty::Int(..) | ty::Float(..))
376+
}
377+
UnOp::Not => {
378+
check_kinds!(
379+
a,
380+
"Cannot binary not type {:?}",
381+
ty::Int(..) | ty::Uint(..) | ty::Bool
382+
);
383+
}
384+
}
385+
}
386+
Rvalue::ShallowInitBox(operand, _) => {
387+
let a = operand.ty(&self.body.local_decls, self.tcx);
388+
check_kinds!(a, "Cannot shallow init type {:?}", ty::RawPtr(..));
389+
}
390+
_ => {}
391+
}
392+
self.super_rvalue(rvalue, location);
393+
}
394+
395+
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
396+
match &statement.kind {
397+
StatementKind::Assign(box (dest, rvalue)) => {
398+
// LHS and RHS of the assignment must have the same type.
399+
let left_ty = dest.ty(&self.body.local_decls, self.tcx).ty;
400+
let right_ty = rvalue.ty(&self.body.local_decls, self.tcx);
401+
if !self.mir_assign_valid_types(right_ty, left_ty) {
402+
self.fail(
403+
location,
404+
format!(
405+
"encountered `{:?}` with incompatible types:\n\
406+
left-hand side has type: {}\n\
407+
right-hand side has type: {}",
408+
statement.kind, left_ty, right_ty,
409+
),
410+
);
411+
}
412+
// FIXME(JakobDegen): Check this for all rvalues, not just this one.
413+
if let Rvalue::Use(Operand::Copy(src) | Operand::Move(src)) = rvalue {
414+
// The sides of an assignment must not alias. Currently this just checks whether
415+
// the places are identical.
416+
if dest == src {
417+
self.fail(
418+
location,
419+
"encountered `Assign` statement with overlapping memory",
420+
);
421+
}
295422
}
296423
}
297424
StatementKind::AscribeUserType(..) => {
@@ -512,6 +639,9 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
512639
}
513640
}
514641
TerminatorKind::Yield { resume, drop, .. } => {
642+
if self.body.generator.is_none() {
643+
self.fail(location, "`Yield` cannot appear outside generator bodies");
644+
}
515645
if self.mir_phase >= MirPhase::GeneratorsLowered {
516646
self.fail(location, "`Yield` should have been replaced by generator lowering");
517647
}
@@ -551,18 +681,29 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
551681
}
552682
}
553683
TerminatorKind::GeneratorDrop => {
684+
if self.body.generator.is_none() {
685+
self.fail(location, "`GeneratorDrop` cannot appear outside generator bodies");
686+
}
554687
if self.mir_phase >= MirPhase::GeneratorsLowered {
555688
self.fail(
556689
location,
557690
"`GeneratorDrop` should have been replaced by generator lowering",
558691
);
559692
}
560693
}
561-
// Nothing to validate for these.
562-
TerminatorKind::Resume
563-
| TerminatorKind::Abort
564-
| TerminatorKind::Return
565-
| TerminatorKind::Unreachable => {}
694+
TerminatorKind::Resume | TerminatorKind::Abort => {
695+
let bb = location.block;
696+
if !self.body.basic_blocks()[bb].is_cleanup {
697+
self.fail(location, "Cannot `Resume` or `Abort` from non-cleanup basic block")
698+
}
699+
}
700+
TerminatorKind::Return => {
701+
let bb = location.block;
702+
if self.body.basic_blocks()[bb].is_cleanup {
703+
self.fail(location, "Cannot `Return` from cleanup basic block")
704+
}
705+
}
706+
TerminatorKind::Unreachable => {}
566707
}
567708

568709
self.super_terminator(terminator, location);

compiler/rustc_middle/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
#![feature(unwrap_infallible)]
6060
#![feature(decl_macro)]
6161
#![feature(drain_filter)]
62+
#![feature(intra_doc_pointers)]
6263
#![recursion_limit = "512"]
6364
#![allow(rustc::potential_query_instability)]
6465

0 commit comments

Comments
 (0)