Skip to content

Commit 673d7ca

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 e3807d0 + 887fbd9 commit 673d7ca

File tree

13 files changed

+463
-49
lines changed

13 files changed

+463
-49
lines changed

src/librustc_codegen_llvm/debuginfo/metadata.rs

+124-39
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
@@ -422,6 +422,9 @@ struct LivenessInfo {
422422
/// The set of saved locals live at each suspension point.
423423
live_locals_at_suspension_points: Vec<BitSet<GeneratorSavedLocal>>,
424424

425+
/// Parallel vec to the above with SourceInfo for each yield terminator.
426+
source_info_at_suspension_points: Vec<SourceInfo>,
427+
425428
/// For every saved local, the set of other saved locals that are
426429
/// storage-live at the same time as this local. We cannot overlap locals in
427430
/// the layout which have conflicting storage.
@@ -473,6 +476,7 @@ fn locals_live_across_suspend_points(
473476

474477
let mut storage_liveness_map = IndexVec::from_elem(None, body.basic_blocks());
475478
let mut live_locals_at_suspension_points = Vec::new();
479+
let mut source_info_at_suspension_points = Vec::new();
476480
let mut live_locals_at_any_suspension_point = BitSet::new_empty(body.local_decls.len());
477481

478482
for (block, data) in body.basic_blocks().iter_enumerated() {
@@ -518,6 +522,7 @@ fn locals_live_across_suspend_points(
518522
live_locals_at_any_suspension_point.union(&live_locals);
519523

520524
live_locals_at_suspension_points.push(live_locals);
525+
source_info_at_suspension_points.push(data.terminator().source_info);
521526
}
522527
}
523528

@@ -541,6 +546,7 @@ fn locals_live_across_suspend_points(
541546
LivenessInfo {
542547
saved_locals,
543548
live_locals_at_suspension_points,
549+
source_info_at_suspension_points,
544550
storage_conflicts,
545551
storage_liveness: storage_liveness_map,
546552
}
@@ -754,6 +760,7 @@ fn compute_layout<'tcx>(
754760
let LivenessInfo {
755761
saved_locals,
756762
live_locals_at_suspension_points,
763+
source_info_at_suspension_points,
757764
storage_conflicts,
758765
storage_liveness,
759766
} = liveness;
@@ -768,7 +775,18 @@ fn compute_layout<'tcx>(
768775
}
769776

770777
// Leave empty variants for the UNRESUMED, RETURNED, and POISONED states.
778+
// In debuginfo, these will correspond to the beginning (UNRESUMED) or end
779+
// (RETURNED, POISONED) of the function.
771780
const RESERVED_VARIANTS: usize = 3;
781+
let body_span = body.source_scopes[OUTERMOST_SOURCE_SCOPE].span;
782+
let mut variant_source_info: IndexVec<VariantIdx, SourceInfo> = [
783+
SourceInfo::outermost(body_span.shrink_to_lo()),
784+
SourceInfo::outermost(body_span.shrink_to_hi()),
785+
SourceInfo::outermost(body_span.shrink_to_hi()),
786+
]
787+
.iter()
788+
.copied()
789+
.collect();
772790

773791
// Build the generator variant field list.
774792
// Create a map from local indices to generator struct indices.
@@ -787,11 +805,13 @@ fn compute_layout<'tcx>(
787805
remap.entry(locals[saved_local]).or_insert((tys[saved_local], variant_index, idx));
788806
}
789807
variant_fields.push(fields);
808+
variant_source_info.push(source_info_at_suspension_points[suspension_point_idx]);
790809
}
791810
debug!("generator variant_fields = {:?}", variant_fields);
792811
debug!("generator storage_conflicts = {:#?}", storage_conflicts);
793812

794-
let layout = GeneratorLayout { field_tys: tys, variant_fields, storage_conflicts };
813+
let layout =
814+
GeneratorLayout { field_tys: tys, variant_fields, variant_source_info, storage_conflicts };
795815

796816
(remap, layout, storage_liveness)
797817
}

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)?;
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
// only-msvc
9+
10+
async fn foo() {}
11+
async fn async_fn_test() {
12+
foo().await;
13+
let s = String::from("foo");
14+
foo().await;
15+
}
16+
17+
// FIXME: No way to reliably check the filename.
18+
19+
// CHECK-DAG: [[ASYNC_FN:!.*]] = !DINamespace(name: "async_fn_test"
20+
// CHECK-DAG: [[GEN:!.*]] = !DICompositeType(tag: DW_TAG_union_type, name: "generator-0", scope: [[ASYNC_FN]], {{.*}}flags: DIFlagArtificial
21+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, scope: [[GEN]],
22+
// For brevity, we only check the struct name and members of the last variant.
23+
// CHECK-SAME: file: [[FILE:![0-9]*]], line: 11,
24+
// CHECK-SAME: flags: DIFlagArtificial
25+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, scope: [[GEN]],
26+
// CHECK-SAME: file: [[FILE]], line: 15,
27+
// CHECK-SAME: flags: DIFlagArtificial
28+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, scope: [[GEN]],
29+
// CHECK-SAME: file: [[FILE]], line: 15,
30+
// CHECK-SAME: flags: DIFlagArtificial
31+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, scope: [[GEN]],
32+
// CHECK-SAME: file: [[FILE]], line: 12,
33+
// CHECK-SAME: flags: DIFlagArtificial
34+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, scope: [[GEN]],
35+
// CHECK-SAME: file: [[FILE]], line: 14,
36+
// CHECK-SAME: baseType: [[VARIANT:![0-9]*]]
37+
// CHECK-SAME: flags: DIFlagArtificial
38+
// CHECK: [[S1:!.*]] = !DICompositeType(tag: DW_TAG_structure_type, name: "Suspend1", scope: [[ASYNC_FN]],
39+
// CHECK-SAME: flags: DIFlagArtificial
40+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "RUST$ENUM$DISR", scope: [[S1]],
41+
// CHECK-SAME: flags: DIFlagArtificial
42+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "s", scope: [[S1]]
43+
// CHECK-NOT: flags: DIFlagArtificial
44+
// CHECK-SAME: )
45+
46+
fn main() {
47+
let _dummy = async_fn_test();
48+
}

src/test/codegen/async-fn-debug.rs

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Verify debuginfo for async fn:
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+
// ignore-msvc
9+
10+
async fn foo() {}
11+
async fn async_fn_test() {
12+
foo().await;
13+
let s = String::from("foo");
14+
foo().await;
15+
}
16+
17+
// FIXME: No way to reliably check the filename.
18+
19+
// CHECK-DAG: [[ASYNC_FN:!.*]] = !DINamespace(name: "async_fn_test"
20+
// CHECK-DAG: [[GEN:!.*]] = !DICompositeType(tag: DW_TAG_structure_type, name: "generator-0", scope: [[ASYNC_FN]], {{.*}}flags: DIFlagArtificial
21+
// CHECK: [[VARIANT:!.*]] = !DICompositeType(tag: DW_TAG_variant_part, scope: [[ASYNC_FN]],
22+
// CHECK-SAME: flags: DIFlagArtificial
23+
// CHECK-SAME: discriminator: [[DISC:![0-9]*]]
24+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "0", scope: [[VARIANT]],
25+
// CHECK-SAME: file: [[FILE:![0-9]*]], line: 11,
26+
// CHECK-SAME: flags: DIFlagArtificial
27+
// CHECK: {{!.*}} = !DICompositeType(tag: DW_TAG_structure_type, name: "Unresumed", scope: [[GEN]],
28+
// CHECK-SAME: flags: DIFlagArtificial
29+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "1", scope: [[VARIANT]],
30+
// CHECK-SAME: file: [[FILE]], line: 15,
31+
// CHECK-SAME: flags: DIFlagArtificial
32+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "2", scope: [[VARIANT]],
33+
// CHECK-SAME: file: [[FILE]], line: 15,
34+
// CHECK-SAME: flags: DIFlagArtificial
35+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "3", scope: [[VARIANT]],
36+
// CHECK-SAME: file: [[FILE]], line: 12,
37+
// CHECK-SAME: flags: DIFlagArtificial
38+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "4", scope: [[VARIANT]],
39+
// CHECK-SAME: file: [[FILE]], line: 14,
40+
// CHECK-SAME: flags: DIFlagArtificial
41+
// CHECK: [[S1:!.*]] = !DICompositeType(tag: DW_TAG_structure_type, name: "Suspend1", scope: [[GEN]],
42+
// CHECK-SAME: flags: DIFlagArtificial
43+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "s", scope: [[S1]]
44+
// CHECK-NOT: flags: DIFlagArtificial
45+
// CHECK-SAME: )
46+
// CHECK: [[DISC]] = !DIDerivedType(tag: DW_TAG_member, name: "__state", scope: [[ASYNC_FN]],
47+
// CHECK-SAME: flags: DIFlagArtificial
48+
49+
fn main() {
50+
let _dummy = async_fn_test();
51+
}
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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
8+
// only-msvc
9+
10+
#![feature(generators, generator_trait)]
11+
use std::ops::Generator;
12+
13+
fn generator_test() -> impl Generator<Yield = i32, Return = ()> {
14+
|| {
15+
yield 0;
16+
let s = String::from("foo");
17+
yield 1;
18+
}
19+
}
20+
21+
// FIXME: No way to reliably check the filename.
22+
23+
// CHECK-DAG: [[GEN_FN:!.*]] = !DINamespace(name: "generator_test"
24+
// CHECK-DAG: [[GEN:!.*]] = !DICompositeType(tag: DW_TAG_union_type, name: "generator-0", scope: [[GEN_FN]], {{.*}}flags: DIFlagArtificial
25+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, scope: [[GEN]],
26+
// For brevity, we only check the struct name and members of the last variant.
27+
// CHECK-SAME: file: [[FILE:![0-9]*]], line: 14,
28+
// CHECK-SAME: flags: DIFlagArtificial
29+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, scope: [[GEN]],
30+
// CHECK-SAME: file: [[FILE]], line: 18,
31+
// CHECK-SAME: flags: DIFlagArtificial
32+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, scope: [[GEN]],
33+
// CHECK-SAME: file: [[FILE]], line: 18,
34+
// CHECK-SAME: flags: DIFlagArtificial
35+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, scope: [[GEN]],
36+
// CHECK-SAME: file: [[FILE]], line: 15,
37+
// CHECK-SAME: flags: DIFlagArtificial
38+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, scope: [[GEN]],
39+
// CHECK-SAME: file: [[FILE]], line: 17,
40+
// CHECK-SAME: baseType: [[VARIANT:![0-9]*]]
41+
// CHECK-SAME: flags: DIFlagArtificial
42+
// CHECK: [[S1:!.*]] = !DICompositeType(tag: DW_TAG_structure_type, name: "Suspend1", scope: [[GEN_FN]],
43+
// CHECK-SAME: flags: DIFlagArtificial
44+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "RUST$ENUM$DISR", scope: [[S1]],
45+
// CHECK-SAME: flags: DIFlagArtificial
46+
// CHECK: {{!.*}} = !DIDerivedType(tag: DW_TAG_member, name: "s", scope: [[S1]]
47+
// CHECK-NOT: flags: DIFlagArtificial
48+
// CHECK-SAME: )
49+
50+
fn main() {
51+
let _dummy = generator_test();
52+
}

0 commit comments

Comments
 (0)