Skip to content

Commit 50cbc1a

Browse files
authored
Rollup merge of rust-lang#73460 - tmandry:variant-lineinfo, r=oli-obk
Emit line info for generator variants Debuggers should be able to read a generator / async fn state machine and show the line it's suspended at. Eventually, this could grow into an "async stack trace" feature of sorts. While no debugger support this for Rust today, this PR adds the debuginfo necessary for that support to exist. [This gist](https://gist.github.com/tmandry/6d7004fa008684f76809208847459f9b) shows the resulting debuginfo for a simple example. Here's a snippet: ``` 0x00000986: DW_TAG_variant DW_AT_discr_value (0x03) 0x00000988: DW_TAG_member DW_AT_name ("3") DW_AT_type (0x000009bc "Suspend0") DW_AT_decl_file ("/home/tmandry/code/playground/generator-simple.rs") DW_AT_decl_line (6) DW_AT_alignment (8) DW_AT_data_member_location (0x00) ``` The file and line have been added here. The line currently points to the beginning of the statement containing the yield (or await), because that's what the MIR source info points to for the yield terminator. (We may want to point to the yield or await line specifically, but that can be done independently of this change.) Debuggers don't know how to use this kind of info yet. However, we're hoping to experiment with adding such support to Fuchsia's debugger. It would be exciting if someone were interested in adding similar to support to gdb/lldb. r? @oli-obk cc @eddyb @jonas-schievink Part of rust-lang#73524.
2 parents f4be902 + 5fbb6ae commit 50cbc1a

File tree

10 files changed

+341
-41
lines changed

10 files changed

+341
-41
lines changed

src/librustc_codegen_llvm/debuginfo/metadata.rs

+114-31
Large diffs are not rendered by default.

src/librustc_codegen_llvm/type_of.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ fn uncached_llvm_type<'a, 'tcx>(
7070
write!(&mut name, "::{}", def.variants[index].ident).unwrap();
7171
}
7272
}
73-
if let (&ty::Generator(_, substs, _), &Variants::Single { index })
73+
if let (&ty::Generator(_, _, _), &Variants::Single { index })
7474
= (&layout.ty.kind, &layout.variants)
7575
{
76-
write!(&mut name, "::{}", substs.as_generator().variant_name(index)).unwrap();
76+
write!(&mut name, "::{}", ty::GeneratorSubsts::variant_name(index)).unwrap();
7777
}
7878
Some(name)
7979
}

src/librustc_index/bit_set.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ impl<T: Idx> GrowableBitSet<T> {
700700
///
701701
/// All operations that involve a row and/or column index will panic if the
702702
/// index exceeds the relevant bound.
703-
#[derive(Clone, Debug, Eq, PartialEq, RustcDecodable, RustcEncodable)]
703+
#[derive(Clone, Eq, PartialEq, RustcDecodable, RustcEncodable)]
704704
pub struct BitMatrix<R: Idx, C: Idx> {
705705
num_rows: usize,
706706
num_columns: usize,
@@ -876,6 +876,22 @@ impl<R: Idx, C: Idx> BitMatrix<R, C> {
876876
}
877877
}
878878

879+
impl<R: Idx, C: Idx> fmt::Debug for BitMatrix<R, C> {
880+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
881+
/// Forces its contents to print in regular mode instead of alternate mode.
882+
struct OneLinePrinter<T>(T);
883+
impl<T: fmt::Debug> fmt::Debug for OneLinePrinter<T> {
884+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
885+
write!(fmt, "{:?}", self.0)
886+
}
887+
}
888+
889+
write!(fmt, "BitMatrix({}x{}) ", self.num_rows, self.num_columns)?;
890+
let items = self.rows().flat_map(|r| self.iter(r).map(move |c| (r, c)));
891+
fmt.debug_set().entries(items.map(OneLinePrinter)).finish()
892+
}
893+
}
894+
879895
/// A fixed-column-size, variable-row-size 2D bit matrix with a moderately
880896
/// sparse representation.
881897
///

src/librustc_middle/mir/query.rs

+63-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use rustc_index::vec::IndexVec;
1010
use rustc_span::{Span, Symbol};
1111
use rustc_target::abi::VariantIdx;
1212
use smallvec::SmallVec;
13+
use std::cell::Cell;
14+
use std::fmt::{self, Debug};
1315

1416
use super::{Field, SourceInfo};
1517

@@ -58,7 +60,7 @@ rustc_index::newtype_index! {
5860
}
5961

6062
/// The layout of generator state.
61-
#[derive(Clone, Debug, RustcEncodable, RustcDecodable, HashStable, TypeFoldable)]
63+
#[derive(Clone, RustcEncodable, RustcDecodable, HashStable, TypeFoldable)]
6264
pub struct GeneratorLayout<'tcx> {
6365
/// The type of every local stored inside the generator.
6466
pub field_tys: IndexVec<GeneratorSavedLocal, Ty<'tcx>>,
@@ -67,12 +69,72 @@ pub struct GeneratorLayout<'tcx> {
6769
/// be stored in multiple variants.
6870
pub variant_fields: IndexVec<VariantIdx, IndexVec<Field, GeneratorSavedLocal>>,
6971

72+
/// The source that led to each variant being created (usually, a yield or
73+
/// await).
74+
pub variant_source_info: IndexVec<VariantIdx, SourceInfo>,
75+
7076
/// Which saved locals are storage-live at the same time. Locals that do not
7177
/// have conflicts with each other are allowed to overlap in the computed
7278
/// layout.
7379
pub storage_conflicts: BitMatrix<GeneratorSavedLocal, GeneratorSavedLocal>,
7480
}
7581

82+
impl Debug for GeneratorLayout<'_> {
83+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
84+
/// Prints an iterator of (key, value) tuples as a map.
85+
struct MapPrinter<'a, K, V>(Cell<Option<Box<dyn Iterator<Item = (K, V)> + 'a>>>);
86+
impl<'a, K, V> MapPrinter<'a, K, V> {
87+
fn new(iter: impl Iterator<Item = (K, V)> + 'a) -> Self {
88+
Self(Cell::new(Some(Box::new(iter))))
89+
}
90+
}
91+
impl<'a, K: Debug, V: Debug> Debug for MapPrinter<'a, K, V> {
92+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
93+
fmt.debug_map().entries(self.0.take().unwrap()).finish()
94+
}
95+
}
96+
97+
/// Prints the generator variant name.
98+
struct GenVariantPrinter(VariantIdx);
99+
impl From<VariantIdx> for GenVariantPrinter {
100+
fn from(idx: VariantIdx) -> Self {
101+
GenVariantPrinter(idx)
102+
}
103+
}
104+
impl Debug for GenVariantPrinter {
105+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
106+
let variant_name = ty::GeneratorSubsts::variant_name(self.0);
107+
if fmt.alternate() {
108+
write!(fmt, "{:9}({:?})", variant_name, self.0)
109+
} else {
110+
write!(fmt, "{}", variant_name)
111+
}
112+
}
113+
}
114+
115+
/// Forces its contents to print in regular mode instead of alternate mode.
116+
struct OneLinePrinter<T>(T);
117+
impl<T: Debug> Debug for OneLinePrinter<T> {
118+
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
119+
write!(fmt, "{:?}", self.0)
120+
}
121+
}
122+
123+
fmt.debug_struct("GeneratorLayout")
124+
.field("field_tys", &MapPrinter::new(self.field_tys.iter_enumerated()))
125+
.field(
126+
"variant_fields",
127+
&MapPrinter::new(
128+
self.variant_fields
129+
.iter_enumerated()
130+
.map(|(k, v)| (GenVariantPrinter(k), OneLinePrinter(v))),
131+
),
132+
)
133+
.field("storage_conflicts", &self.storage_conflicts)
134+
.finish()
135+
}
136+
}
137+
76138
#[derive(Debug, RustcEncodable, RustcDecodable, HashStable)]
77139
pub struct BorrowCheckResult<'tcx> {
78140
/// All the opaque types that are restricted to concrete types

src/librustc_middle/ty/sty.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -522,8 +522,7 @@ impl<'tcx> GeneratorSubsts<'tcx> {
522522

523523
/// Calls `f` with a reference to the name of the enumerator for the given
524524
/// variant `v`.
525-
#[inline]
526-
pub fn variant_name(self, v: VariantIdx) -> Cow<'static, str> {
525+
pub fn variant_name(v: VariantIdx) -> Cow<'static, str> {
527526
match v.as_usize() {
528527
Self::UNRESUMED => Cow::from(Self::UNRESUMED_NAME),
529528
Self::RETURNED => Cow::from(Self::RETURNED_NAME),

src/librustc_mir/transform/generator.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,9 @@ struct LivenessInfo {
426426
/// The set of saved locals live at each suspension point.
427427
live_locals_at_suspension_points: Vec<BitSet<GeneratorSavedLocal>>,
428428

429+
/// Parallel vec to the above with SourceInfo for each yield terminator.
430+
source_info_at_suspension_points: Vec<SourceInfo>,
431+
429432
/// For every saved local, the set of other saved locals that are
430433
/// storage-live at the same time as this local. We cannot overlap locals in
431434
/// the layout which have conflicting storage.
@@ -477,6 +480,7 @@ fn locals_live_across_suspend_points(
477480

478481
let mut storage_liveness_map = IndexVec::from_elem(None, body.basic_blocks());
479482
let mut live_locals_at_suspension_points = Vec::new();
483+
let mut source_info_at_suspension_points = Vec::new();
480484
let mut live_locals_at_any_suspension_point = BitSet::new_empty(body.local_decls.len());
481485

482486
for (block, data) in body.basic_blocks().iter_enumerated() {
@@ -522,6 +526,7 @@ fn locals_live_across_suspend_points(
522526
live_locals_at_any_suspension_point.union(&live_locals);
523527

524528
live_locals_at_suspension_points.push(live_locals);
529+
source_info_at_suspension_points.push(data.terminator().source_info);
525530
}
526531
}
527532
debug!("live_locals_anywhere = {:?}", live_locals_at_any_suspension_point);
@@ -543,6 +548,7 @@ fn locals_live_across_suspend_points(
543548
LivenessInfo {
544549
live_locals: live_locals_at_any_suspension_point,
545550
live_locals_at_suspension_points,
551+
source_info_at_suspension_points,
546552
storage_conflicts,
547553
storage_liveness: storage_liveness_map,
548554
}
@@ -740,6 +746,7 @@ fn compute_layout<'tcx>(
740746
let LivenessInfo {
741747
live_locals,
742748
live_locals_at_suspension_points,
749+
source_info_at_suspension_points,
743750
storage_conflicts,
744751
storage_liveness,
745752
} = locals_live_across_suspend_points(tcx, body, source, always_live_locals, movable);
@@ -756,7 +763,18 @@ fn compute_layout<'tcx>(
756763
}
757764

758765
// Leave empty variants for the UNRESUMED, RETURNED, and POISONED states.
766+
// In debuginfo, these will correspond to the beginning (UNRESUMED) or end
767+
// (RETURNED, POISONED) of the function.
759768
const RESERVED_VARIANTS: usize = 3;
769+
let body_span = body.source_scopes[OUTERMOST_SOURCE_SCOPE].span;
770+
let mut variant_source_info: IndexVec<VariantIdx, SourceInfo> = [
771+
SourceInfo::outermost(body_span.shrink_to_lo()),
772+
SourceInfo::outermost(body_span.shrink_to_hi()),
773+
SourceInfo::outermost(body_span.shrink_to_hi()),
774+
]
775+
.iter()
776+
.copied()
777+
.collect();
760778

761779
// Build the generator variant field list.
762780
// Create a map from local indices to generator struct indices.
@@ -775,11 +793,13 @@ fn compute_layout<'tcx>(
775793
remap.entry(locals[saved_local]).or_insert((tys[saved_local], variant_index, idx));
776794
}
777795
variant_fields.push(fields);
796+
variant_source_info.push(source_info_at_suspension_points[suspension_point_idx]);
778797
}
779798
debug!("generator variant_fields = {:?}", variant_fields);
780799
debug!("generator storage_conflicts = {:#?}", storage_conflicts);
781800

782-
let layout = GeneratorLayout { field_tys: tys, variant_fields, storage_conflicts };
801+
let layout =
802+
GeneratorLayout { field_tys: tys, variant_fields, variant_source_info, storage_conflicts };
783803

784804
(remap, layout, storage_liveness)
785805
}

src/librustc_mir/util/pretty.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ fn dump_matched_mir_node<'tcx, F>(
131131
}
132132
writeln!(file, " {} {}", disambiguator, pass_name)?;
133133
if let Some(ref layout) = body.generator_layout {
134-
writeln!(file, "// generator_layout = {:?}", layout)?;
134+
writeln!(file, "/* generator_layout = {:#?} */", layout)?;
135135
}
136136
writeln!(file)?;
137137
extra_data(PassWhere::BeforeCFG, &mut file)?;

src/test/codegen/generator-debug.rs

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Verify debuginfo for generators:
2+
// - Each variant points to the file and line of its yield point
3+
// - The generator types and variants are marked artificial
4+
// - Captured vars from the source are not marked artificial
5+
//
6+
// ignore-tidy-linelength
7+
// compile-flags: -C debuginfo=2 --edition=2018
8+
9+
#![feature(generators, generator_trait)]
10+
use std::ops::Generator;
11+
12+
fn generator_test() -> impl Generator<Yield = i32, Return = ()> {
13+
|| {
14+
yield 0;
15+
let s = String::from("foo");
16+
yield 1;
17+
}
18+
}
19+
20+
async fn foo() {}
21+
async fn async_fn_test() {
22+
foo().await;
23+
let s = String::from("foo");
24+
foo().await;
25+
}
26+
27+
// FIXME: We need "checksum" to prevent matching with the wrong (duplicate) file
28+
// metadata, even when -C codegen-units=1.
29+
// CHECK: [[FILE:!.*]] = !DIFile(filename: "{{.*}}/generator-debug.rs", {{.*}}, checksum:
30+
31+
// CHECK: [[GEN:!.*]] = !DICompositeType(tag: DW_TAG_structure_type, name: "generator-0", scope: [[FN:![0-9]*]],
32+
// CHECK-SAME: flags: DIFlagArtificial
33+
// CHECK: [[FN]] = !DINamespace(name: "generator_test"
34+
// CHECK: [[VARIANT:!.*]] = !DICompositeType(tag: DW_TAG_variant_part, scope: [[FN]],
35+
// CHECK-SAME: flags: DIFlagArtificial
36+
// CHECK-SAME: discriminator: [[DISC:![0-9]*]]
37+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "0", scope: [[VARIANT]],
38+
// CHECK-SAME: file: [[FILE]], line: 13,
39+
// CHECK-SAME: flags: DIFlagArtificial
40+
// CHECK: {{!.*}} = !DICompositeType(tag: DW_TAG_structure_type, name: "Unresumed", scope: [[GEN]],
41+
// CHECK-SAME: flags: DIFlagArtificial
42+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "1", scope: [[VARIANT]],
43+
// CHECK-SAME: file: [[FILE]], line: 17,
44+
// CHECK-SAME: flags: DIFlagArtificial
45+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "2", scope: [[VARIANT]],
46+
// CHECK-SAME: file: [[FILE]], line: 17,
47+
// CHECK-SAME: flags: DIFlagArtificial
48+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "3", scope: [[VARIANT]],
49+
// CHECK-SAME: file: [[FILE]], line: 14,
50+
// CHECK-SAME: flags: DIFlagArtificial
51+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "4", scope: [[VARIANT]],
52+
// CHECK-SAME: file: [[FILE]], line: 16,
53+
// CHECK-SAME: flags: DIFlagArtificial
54+
// CHECK: [[S1:!.*]] = !DICompositeType(tag: DW_TAG_structure_type, name: "Suspend1", scope: [[GEN]],
55+
// CHECK-SAME: flags: DIFlagArtificial
56+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "s", scope: [[S1]]
57+
// CHECK-NOT: flags: DIFlagArtificial
58+
// CHECK-SAME: )
59+
// CHECK: [[DISC]] = !DIDerivedType(tag: DW_TAG_member, name: "__state", scope: [[FN]],
60+
// CHECK-SAME: flags: DIFlagArtificial
61+
62+
// CHECK: [[GEN:!.*]] = !DICompositeType(tag: DW_TAG_structure_type, name: "generator-0", scope: [[FN:![0-9]*]],
63+
// CHECK-SAME: flags: DIFlagArtificial
64+
// CHECK: [[FN]] = !DINamespace(name: "async_fn_test"
65+
// CHECK: [[VARIANT:!.*]] = !DICompositeType(tag: DW_TAG_variant_part, scope: [[FN]],
66+
// CHECK-SAME: flags: DIFlagArtificial
67+
// CHECK-SAME: discriminator: [[DISC:![0-9]*]]
68+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "0", scope: [[VARIANT]],
69+
// CHECK-SAME: file: [[FILE]], line: 21,
70+
// CHECK-SAME: flags: DIFlagArtificial
71+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "1", scope: [[VARIANT]],
72+
// CHECK-SAME: file: [[FILE]], line: 25,
73+
// CHECK-SAME: flags: DIFlagArtificial
74+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "2", scope: [[VARIANT]],
75+
// CHECK-SAME: file: [[FILE]], line: 25,
76+
// CHECK-SAME: flags: DIFlagArtificial
77+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "3", scope: [[VARIANT]],
78+
// CHECK-SAME: file: [[FILE]], line: 22,
79+
// CHECK-SAME: flags: DIFlagArtificial
80+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "4", scope: [[VARIANT]],
81+
// CHECK-SAME: file: [[FILE]], line: 24,
82+
// CHECK-SAME: flags: DIFlagArtificial
83+
// CHECK: [[S1:!.*]] = !DICompositeType(tag: DW_TAG_structure_type, name: "Suspend1", scope: [[GEN]],
84+
// CHECK-SAME: flags: DIFlagArtificial
85+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "s", scope: [[S1]]
86+
// CHECK-NOT: flags: DIFlagArtificial
87+
// CHECK-SAME: )
88+
// CHECK: [[DISC]] = !DIDerivedType(tag: DW_TAG_member, name: "__state", scope: [[FN]],
89+
// CHECK-SAME: flags: DIFlagArtificial
90+
91+
fn main() {
92+
let _dummy = generator_test();
93+
let _dummy = async_fn_test();
94+
}

src/test/mir-opt/generator-drop-cleanup/rustc.main-{{closure}}.generator_drop.0.mir

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
// MIR for `main::{{closure}}#0` 0 generator_drop
2-
// generator_layout = GeneratorLayout { field_tys: [std::string::String], variant_fields: [[], [], [], [_0]], storage_conflicts: BitMatrix { num_rows: 1, num_columns: 1, words: [1], marker: PhantomData } }
2+
/* generator_layout = GeneratorLayout {
3+
field_tys: {
4+
_0: std::string::String,
5+
},
6+
variant_fields: {
7+
Unresumed(0): [],
8+
Returned (1): [],
9+
Panicked (2): [],
10+
Suspend0 (3): [_0],
11+
},
12+
storage_conflicts: BitMatrix(1x1) {
13+
(_0, _0),
14+
},
15+
} */
316

417
fn main::{{closure}}#0(_1: *mut [generator@$DIR/generator-drop-cleanup.rs:10:15: 13:6 {std::string::String, ()}]) -> () {
518
let mut _0: (); // return place in scope 0 at $DIR/generator-drop-cleanup.rs:10:15: 13:6

src/test/mir-opt/generator-tiny/rustc.main-{{closure}}.generator_resume.0.mir

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
// MIR for `main::{{closure}}#0` 0 generator_resume
2-
// generator_layout = GeneratorLayout { field_tys: [HasDrop], variant_fields: [[], [], [], [_0]], storage_conflicts: BitMatrix { num_rows: 1, num_columns: 1, words: [1], marker: PhantomData } }
2+
/* generator_layout = GeneratorLayout {
3+
field_tys: {
4+
_0: HasDrop,
5+
},
6+
variant_fields: {
7+
Unresumed(0): [],
8+
Returned (1): [],
9+
Panicked (2): [],
10+
Suspend0 (3): [_0],
11+
},
12+
storage_conflicts: BitMatrix(1x1) {
13+
(_0, _0),
14+
},
15+
} */
316

417
fn main::{{closure}}#0(_1: std::pin::Pin<&mut [generator@$DIR/generator-tiny.rs:19:16: 25:6 {u8, HasDrop, ()}]>, _2: u8) -> std::ops::GeneratorState<(), ()> {
518
debug _x => _10; // in scope 0 at $DIR/generator-tiny.rs:19:17: 19:19

0 commit comments

Comments
 (0)