Skip to content

Commit 3a53860

Browse files
Rollup merge of #156672 - cjgillot:coroutine-qol, r=oli-obk
Misc improvements to coroutine transform code Several quality-of-life improvements: A dedicated mir-opt directory for coroutines, there are very few tests right now, but more should come. A dedicated pretty-printer for coroutine layout. In particular, it does not rely on `Ty as Debug` which has unstable output. This is important for async fns which capture opaque types, in particular other async fns. A drive-by simplification. Last, I change how the coroutine entry block is inserted. The current implementation shifts everything by 1. I prefer swapping with the current entry, which makes debugging and MIR diffing much easier.
2 parents e8659d7 + f6a5b0b commit 3a53860

45 files changed

Lines changed: 2069 additions & 1328 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

compiler/rustc_middle/src/mir/pretty.rs

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::mir::interpret::{
1515
};
1616
use crate::mir::visit::Visitor;
1717
use crate::mir::*;
18+
use crate::ty::CoroutineArgsExt;
1819

1920
const INDENT: &str = " ";
2021
/// Alignment for lining up comments following MIR statements
@@ -185,9 +186,6 @@ impl<'a, 'tcx> MirDumper<'a, 'tcx> {
185186
Some(promoted) => write!(w, "::{promoted:?}`")?,
186187
}
187188
writeln!(w, " {} {}", self.disambiguator, self.pass_name)?;
188-
if let Some(ref layout) = body.coroutine_layout_raw() {
189-
writeln!(w, "/* coroutine_layout = {layout:#?} */")?;
190-
}
191189
writeln!(w)?;
192190
(self.writer.extra_data)(PassWhere::BeforeCFG, w)?;
193191
write_user_type_annotations(self.tcx(), body, w)?;
@@ -429,6 +427,31 @@ fn write_scope_tree(
429427
}
430428
}
431429

430+
// Coroutine debuginfo.
431+
if let Some(layout) = body.coroutine_layout_raw() {
432+
for (field, name) in layout.field_names.iter_enumerated() {
433+
let source_info = layout.field_tys[field].source_info;
434+
if let Some(name) = name
435+
&& source_info.scope == parent
436+
{
437+
let indented_debug_info =
438+
format!("{0:1$}coroutine debug {2} => {3:?};", INDENT, indent, name, field);
439+
440+
if options.include_extra_comments {
441+
writeln!(
442+
w,
443+
"{0:1$} // in {2}",
444+
indented_debug_info,
445+
ALIGN,
446+
comment(tcx, source_info),
447+
)?;
448+
} else {
449+
writeln!(w, "{indented_debug_info}")?;
450+
}
451+
}
452+
}
453+
}
454+
432455
// Local variable types.
433456
for (local, local_decl) in body.local_decls.iter_enumerated() {
434457
if (1..body.arg_count + 1).contains(&local.index()) {
@@ -530,6 +553,50 @@ impl Debug for VarDebugInfo<'_> {
530553
}
531554
}
532555

556+
fn write_coroutine_layout<'tcx>(
557+
tcx: TyCtxt<'tcx>,
558+
layout: &CoroutineLayout<'_>,
559+
w: &mut dyn io::Write,
560+
options: PrettyPrintMirOptions,
561+
) -> io::Result<()> {
562+
let CoroutineLayout {
563+
field_tys,
564+
field_names: _, // Dumped in scope tree with debug info.
565+
variant_fields,
566+
variant_source_info,
567+
storage_conflicts,
568+
} = layout;
569+
570+
writeln!(w, "{INDENT}coroutine layout {{")?;
571+
572+
for (field, CoroutineSavedTy { ty, source_info, ignore_for_traits }) in
573+
field_tys.iter_enumerated()
574+
{
575+
let ignore_for_traits = if *ignore_for_traits { " (ignored for traits)" } else { "" };
576+
let indented_body = format!("{INDENT}{INDENT}field {field:?}: {ty}{ignore_for_traits};",);
577+
if options.include_extra_comments {
578+
writeln!(w, "{0:ALIGN$} // in {1}", indented_body, comment(tcx, *source_info))?;
579+
} else {
580+
writeln!(w, "{}", indented_body)?;
581+
}
582+
}
583+
584+
writeln!(w, "{INDENT}{INDENT}variant_fields = {{")?;
585+
for (variant, fields) in variant_fields.iter_enumerated() {
586+
let variant_name = ty::CoroutineArgs::variant_name(variant);
587+
let header = format!("{INDENT}{INDENT}{INDENT}{variant_name:9}({variant:?}): {fields:?},");
588+
if options.include_extra_comments {
589+
let source_info = variant_source_info[variant];
590+
writeln!(w, "{0:ALIGN$} // in {1}", header, comment(tcx, source_info))?;
591+
} else {
592+
writeln!(w, "{}", header)?;
593+
}
594+
}
595+
writeln!(w, "{INDENT}{INDENT}}}")?;
596+
writeln!(w, "{INDENT}{INDENT}storage_conflicts = {storage_conflicts:?}")?;
597+
writeln!(w, "{INDENT}}}")
598+
}
599+
533600
/// Write out a human-readable textual representation of the MIR's `fn` type and the types of its
534601
/// local variables (both user-defined bindings and compiler temporaries).
535602
fn write_mir_intro<'tcx>(
@@ -541,6 +608,10 @@ fn write_mir_intro<'tcx>(
541608
write_mir_sig(tcx, body, w)?;
542609
writeln!(w, "{{")?;
543610

611+
if let Some(ref layout) = body.coroutine_layout_raw() {
612+
write_coroutine_layout(tcx, layout, w, options)?;
613+
}
614+
544615
// construct a scope tree and write it out
545616
let mut scope_tree: FxHashMap<SourceScope, Vec<SourceScope>> = Default::default();
546617
for (index, scope_data) in body.source_scopes.iter_enumerated() {

compiler/rustc_mir_transform/src/coroutine.rs

Lines changed: 27 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,31 +1077,42 @@ fn compute_layout<'tcx>(
10771077
/// Replaces the entry point of `body` with a block that switches on the coroutine discriminant and
10781078
/// dispatches to blocks according to `cases`.
10791079
///
1080-
/// After this function, the former entry point of the function will be bb1.
1080+
/// After this function, the former entry point of the function will be the last block.
10811081
fn insert_switch<'tcx>(
10821082
body: &mut Body<'tcx>,
10831083
cases: Vec<(usize, BasicBlock)>,
10841084
transform: &TransformVisitor<'tcx>,
10851085
default_block: BasicBlock,
10861086
) {
10871087
let (assign, discr) = transform.get_discr(body);
1088-
let switch_targets =
1089-
SwitchTargets::new(cases.iter().map(|(i, bb)| ((*i) as u128, *bb)), default_block);
1090-
let switch = TerminatorKind::SwitchInt { discr: Operand::Move(discr), targets: switch_targets };
10911088

1092-
let source_info = SourceInfo::outermost(body.span);
1093-
body.basic_blocks_mut().raw.insert(
1094-
0,
1095-
BasicBlockData::new_stmts(
1096-
vec![assign],
1097-
Some(Terminator { source_info, kind: switch }),
1098-
false,
1099-
),
1089+
// MIR validation ensures that no block targets `ENTRY_BLOCK`.
1090+
#[cfg(debug_assertions)]
1091+
for bb in body.basic_blocks.iter() {
1092+
for target in bb.terminator().successors() {
1093+
assert_ne!(target, START_BLOCK);
1094+
}
1095+
}
1096+
1097+
// Add the switch as entry block, and put the former entry block at the end.
1098+
let former_entry = std::mem::replace(
1099+
&mut body.basic_blocks_mut()[START_BLOCK],
1100+
BasicBlockData::new_stmts(vec![assign], None, false),
11001101
);
1102+
let former_entry = body.basic_blocks_mut().push(former_entry);
11011103

1102-
for b in body.basic_blocks_mut().iter_mut() {
1103-
b.terminator_mut().successors_mut(|target| *target += 1);
1104+
// We may point to `START_BLOCK` in our `cases`, replace it with `former_entry`.
1105+
let mut switch_targets =
1106+
SwitchTargets::new(cases.iter().map(|(i, bb)| ((*i) as u128, *bb)), default_block);
1107+
for bb in switch_targets.all_targets_mut() {
1108+
if *bb == START_BLOCK {
1109+
*bb = former_entry;
1110+
}
11041111
}
1112+
1113+
let switch = TerminatorKind::SwitchInt { discr: Operand::Move(discr), targets: switch_targets };
1114+
body.basic_blocks_mut()[START_BLOCK].terminator =
1115+
Some(Terminator { source_info: SourceInfo::outermost(body.span), kind: switch });
11051116
}
11061117

11071118
fn insert_term_block<'tcx>(body: &mut Body<'tcx>, kind: TerminatorKind<'tcx>) -> BasicBlock {
@@ -1172,41 +1183,8 @@ fn can_unwind<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool {
11721183
return false;
11731184
}
11741185

1175-
// Unwinds can only start at certain terminators.
1176-
for block in body.basic_blocks.iter() {
1177-
match block.terminator().kind {
1178-
// These never unwind.
1179-
TerminatorKind::Goto { .. }
1180-
| TerminatorKind::SwitchInt { .. }
1181-
| TerminatorKind::UnwindTerminate(_)
1182-
| TerminatorKind::Return
1183-
| TerminatorKind::Unreachable
1184-
| TerminatorKind::CoroutineDrop
1185-
| TerminatorKind::FalseEdge { .. }
1186-
| TerminatorKind::FalseUnwind { .. } => {}
1187-
1188-
// Resume will *continue* unwinding, but if there's no other unwinding terminator it
1189-
// will never be reached.
1190-
TerminatorKind::UnwindResume => {}
1191-
1192-
TerminatorKind::Yield { .. } => {
1193-
unreachable!("`can_unwind` called before coroutine transform")
1194-
}
1195-
1196-
// These may unwind.
1197-
TerminatorKind::Drop { .. }
1198-
| TerminatorKind::Call { .. }
1199-
| TerminatorKind::InlineAsm { .. }
1200-
| TerminatorKind::Assert { .. } => return true,
1201-
1202-
TerminatorKind::TailCall { .. } => {
1203-
unreachable!("tail calls can't be present in generators")
1204-
}
1205-
}
1206-
}
1207-
1208-
// If we didn't find an unwinding terminator, the function cannot unwind.
1209-
false
1186+
// If we don't find an unwinding terminator, the function cannot unwind.
1187+
body.basic_blocks.iter().any(|block| block.terminator().unwind().is_some())
12101188
}
12111189

12121190
// Poison the coroutine when it unwinds

tests/mir-opt/building/async_await.a-{closure#0}.coroutine_resume.0.mir

Lines changed: 0 additions & 47 deletions
This file was deleted.

0 commit comments

Comments
 (0)