Skip to content

Commit f1f25c0

Browse files
committed
Improve documentation for MIR terminators
1 parent 8e01cd6 commit f1f25c0

File tree

1 file changed

+121
-27
lines changed

1 file changed

+121
-27
lines changed

compiler/rustc_middle/src/mir/terminator.rs

Lines changed: 121 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,34 @@ impl<'a> Iterator for SwitchTargetsIter<'a> {
105105

106106
impl<'a> ExactSizeIterator for SwitchTargetsIter<'a> {}
107107

108+
/// A note on unwinding: Panics may occur during the execution of some terminators. Depending on the
109+
/// `-C panic` flag, this may either cause the program to abort or the call stack to unwind. Such
110+
/// terminators have a `cleanup: Option<BasicBlock>` field on them. If stack unwinding occurs, then
111+
/// once the current function is reached, execution continues at the given basic block, if any. If
112+
/// `cleanup` is `None` then no cleanup is performed, and the stack continues unwinding. This is
113+
/// equivalent to the execution of a `Resume` terminator.
114+
///
115+
/// The basic block pointed to by a `cleanup` field must have its `cleanup` flag set. `cleanup`
116+
/// basic blocks have a couple restrictions:
117+
/// 1. All `cleanup` fields in them must be `None`.
118+
/// 2. `Return` terminators are not allowed in them. `Abort` and `Unwind` terminators are.
119+
/// 3. All other basic blocks (in the current body) that are reachable from `cleanup` basic blocks
120+
/// must also be `cleanup`. This is a part of the type system and checked statically, so it is
121+
/// still an error to have such an edge in the CFG even if it's known that it won't be taken at
122+
/// runtime.
108123
#[derive(Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq)]
109124
pub enum TerminatorKind<'tcx> {
110-
/// Block should have one successor in the graph; we jump there.
125+
/// Block has one successor; we continue execution there.
111126
Goto { target: BasicBlock },
112127

113-
/// Operand evaluates to an integer; jump depending on its value
114-
/// to one of the targets, and otherwise fallback to `otherwise`.
128+
/// Switches based on the computed value.
129+
///
130+
/// First, evaluates the `discr` operand. The type of the operand must be a signed or unsigned
131+
/// integer, char, or bool, and must match the given type. Then, if the list of switch targets
132+
/// contains the computed value, continues execution at the associated basic block. Otherwise,
133+
/// continues execution at the "otherwise" basic block.
134+
///
135+
/// Target values may not appear more than once.
115136
SwitchInt {
116137
/// The discriminant value being tested.
117138
discr: Operand<'tcx>,
@@ -124,29 +145,62 @@ pub enum TerminatorKind<'tcx> {
124145
targets: SwitchTargets,
125146
},
126147

127-
/// Indicates that the landing pad is finished and unwinding should
128-
/// continue. Emitted by `build::scope::diverge_cleanup`.
148+
/// Indicates that the landing pad is finished and that the process should continue unwinding.
149+
///
150+
/// Like a return, this marks the end of this invocation of the function.
151+
///
152+
/// Only permitted in cleanup blocks. `Resume` is not permitted with `-C unwind=abort` after
153+
/// deaggregation runs.
129154
Resume,
130155

131-
/// Indicates that the landing pad is finished and that the process
132-
/// should abort. Used to prevent unwinding for foreign items.
156+
/// Indicates that the landing pad is finished and that the process should abort.
157+
///
158+
/// Used to prevent unwinding for foreign items or with `-C unwind=abort`. Only permitted in
159+
/// cleanup blocks.
133160
Abort,
134161

135-
/// Indicates a normal return. The return place should have
136-
/// been filled in before this executes. This can occur multiple times
137-
/// in different basic blocks.
162+
/// Returns from the function.
163+
///
164+
/// Like function calls, the exact semantics of returns in Rust are unclear. Returning very
165+
/// likely at least assigns the value currently in the return place (`_0`) to the place
166+
/// specified in the associated `Call` terminator in the calling function, as if assigned via
167+
/// `dest = move _0`. It might additionally do other things, like have side-effects in the
168+
/// aliasing model.
169+
///
170+
/// If the body is a generator body, this has slightly different semantics; it instead causes a
171+
/// `GeneratorState::Returned(_0)` to be created (as if by an `Aggregate` rvalue) and assigned
172+
/// to the return place.
138173
Return,
139174

140175
/// Indicates a terminator that can never be reached.
176+
///
177+
/// Executing this terminator is UB.
141178
Unreachable,
142179

143-
/// Drop the `Place`.
180+
/// The behavior of this statement differs significantly before and after drop elaboration.
181+
/// After drop elaboration, `Drop` executes the drop glue for the specified place, after which
182+
/// it continues execution/unwinds at the given basic blocks. It is possible that executing drop
183+
/// glue is special - this would be part of Rust's memory model. (**FIXME**: due we have an
184+
/// issue tracking if drop glue has any interesting semantics in addition to those of a function
185+
/// call?)
186+
///
187+
/// `Drop` before drop elaboration is a *conditional* execution of the drop glue. Specifically, the
188+
/// `Drop` will be executed if...
189+
///
190+
/// **Needs clarification**: End of that sentence. This in effect should document the exact
191+
/// behavior of drop elaboration. The following sounds vaguely right, but I'm not quite sure:
192+
///
193+
/// > The drop glue is executed if, among all statements executed within this `Body`, an assignment to
194+
/// > the place or one of its "parents" occurred more recently than a move out of it. This does not
195+
/// > consider indirect assignments.
144196
Drop { place: Place<'tcx>, target: BasicBlock, unwind: Option<BasicBlock> },
145197

146-
/// Drop the `Place` and assign the new value over it. This ensures
147-
/// that the assignment to `P` occurs *even if* the destructor for
148-
/// place unwinds. Its semantics are best explained by the
149-
/// elaboration:
198+
/// Drops the place and assigns a new value to it.
199+
///
200+
/// This first performs the exact same operation as the pre drop-elaboration `Drop` terminator;
201+
/// it then additionally assigns the `value` to the `place` as if by an assignment statement.
202+
/// This assignment occurs both in the unwind and the regular code paths. The semantics are best
203+
/// explained by the elaboration:
150204
///
151205
/// ```
152206
/// BB0 {
@@ -170,15 +224,22 @@ pub enum TerminatorKind<'tcx> {
170224
/// }
171225
/// ```
172226
///
173-
/// Note that DropAndReplace is eliminated as part of the `ElaborateDrops` pass.
227+
/// Disallowed after drop elaboration.
174228
DropAndReplace {
175229
place: Place<'tcx>,
176230
value: Operand<'tcx>,
177231
target: BasicBlock,
178232
unwind: Option<BasicBlock>,
179233
},
180234

181-
/// Block ends with a call of a function.
235+
/// Roughly speaking, evaluates the `func` operand and the arguments, and starts execution of
236+
/// the referred to function. The operand types must match the argument types of the function.
237+
/// The return place type must exactly match the return type. The type of the `func` operand
238+
/// must be callable, meaning either a function pointer, a function type, or a closure type.
239+
///
240+
/// **Needs clarification**: The exact semantics of this, see [#71117].
241+
///
242+
/// [#71117]: https://github.com/rust-lang/rust/issues/71117
182243
Call {
183244
/// The function that’s being called.
184245
func: Operand<'tcx>,
@@ -187,7 +248,7 @@ pub enum TerminatorKind<'tcx> {
187248
/// This allows the memory occupied by "by-value" arguments to be
188249
/// reused across function calls without duplicating the contents.
189250
args: Vec<Operand<'tcx>>,
190-
/// Destination for the return value. If some, the call is converging.
251+
/// Destination for the return value. If none, the call necessarily diverges.
191252
destination: Option<(Place<'tcx>, BasicBlock)>,
192253
/// Cleanups to be done if the call unwinds.
193254
cleanup: Option<BasicBlock>,
@@ -199,8 +260,12 @@ pub enum TerminatorKind<'tcx> {
199260
fn_span: Span,
200261
},
201262

202-
/// Jump to the target if the condition has the expected value,
203-
/// otherwise panic with a message and a cleanup target.
263+
/// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`,
264+
/// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some
265+
/// unspecified constant as the function to call, all the operands stored in the `AssertMessage`
266+
/// as parameters, and `None` for the destination. Keep in mind that the `cleanup` path is not
267+
/// necessarily executed even in the case of a panic, for example in `-C panic=abort`. If the
268+
/// assertion does not fail, execution continues at the specified basic block.
204269
Assert {
205270
cond: Operand<'tcx>,
206271
expected: bool,
@@ -209,7 +274,18 @@ pub enum TerminatorKind<'tcx> {
209274
cleanup: Option<BasicBlock>,
210275
},
211276

212-
/// A suspend point.
277+
/// Marks a suspend point.
278+
///
279+
/// Like `Return` terminators in generator bodies, this computes `value` and then a
280+
/// `GeneratorState::Yielded(value)` as if by `Aggregate` rvalue. That value is then assigned to
281+
/// the return place of the function calling this one, and execution continues in the calling
282+
/// function. When next invoked with the same first argument, execution of this function
283+
/// continues at the `resume` basic block, with the second argument written to the `resume_arg`
284+
/// place. If the generator is dropped before then, the `drop` basic block is invoked.
285+
///
286+
/// Not permitted in bodies that are not generator bodies, or after generator lowering.
287+
///
288+
/// **Needs clarification**: What about the evaluation order of the `resume_arg` and `value`?
213289
Yield {
214290
/// The value to return.
215291
value: Operand<'tcx>,
@@ -221,21 +297,39 @@ pub enum TerminatorKind<'tcx> {
221297
drop: Option<BasicBlock>,
222298
},
223299

224-
/// Indicates the end of the dropping of a generator.
300+
/// Indicates the end of dropping a generator.
301+
///
302+
/// Semantically just a `return` (from the generators drop glue). Only permitted in the same situations
303+
/// as `yield`.
304+
///
305+
/// **Needs clarification**: Is that even correct? The generator drop code is always confusing
306+
/// to me, because it's not even really in the current body.
307+
///
308+
/// **Needs clarification**: Are there type system constraints on these terminators? Should
309+
/// there be a "block type" like `cleanup` blocks for them?
225310
GeneratorDrop,
226311

227-
/// A block where control flow only ever takes one real path, but borrowck
228-
/// needs to be more conservative.
312+
/// A block where control flow only ever takes one real path, but borrowck needs to be more
313+
/// conservative.
314+
///
315+
/// At runtime this is semantically just a goto.
316+
///
317+
/// Disallowed after drop elaboration.
229318
FalseEdge {
230319
/// The target normal control flow will take.
231320
real_target: BasicBlock,
232321
/// A block control flow could conceptually jump to, but won't in
233322
/// practice.
234323
imaginary_target: BasicBlock,
235324
},
236-
/// A terminator for blocks that only take one path in reality, but where we
237-
/// reserve the right to unwind in borrowck, even if it won't happen in practice.
238-
/// This can arise in infinite loops with no function calls for example.
325+
326+
/// A terminator for blocks that only take one path in reality, but where we reserve the right
327+
/// to unwind in borrowck, even if it won't happen in practice. This can arise in infinite loops
328+
/// with no function calls for example.
329+
///
330+
/// At runtime this is semantically just a goto.
331+
///
332+
/// Disallowed after drop elaboration.
239333
FalseUnwind {
240334
/// The target normal control flow will take.
241335
real_target: BasicBlock,

0 commit comments

Comments
 (0)