From 44d185b0d00c8ba228aafa789a39b77d7fd11daf Mon Sep 17 00:00:00 2001
From: Kevin Reid <kpreid@switchb.org>
Date: Fri, 22 Mar 2024 16:45:32 -0700
Subject: [PATCH] -Zprint-type-sizes: print the types of awaitees and unnamed
 coroutine locals.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This should assist comprehending the size of coroutines.
In particular, whenever a future is suspended while awaiting another
future, the latter is given the special name `__awaitee`, and now the
type of the awaited future will be printed, allowing identifying
caller/callee — er, I mean, poller/pollee — relationships.

It would be possible to include the type name in more cases, but I
thought that that might be overly verbose (`print-type-sizes` is already
a lot of text) and ordinary named fields or variables are easier for
readers to discover the types of.
---
 compiler/rustc_session/src/code_stats.rs       | 18 ++++++++++++++----
 compiler/rustc_ty_utils/src/layout.rs          | 10 +++++++++-
 .../future-sizes/async-awaiting-fut.stdout     | 14 +++++++-------
 .../async-await/future-sizes/large-arg.stdout  |  6 +++---
 tests/ui/print_type_sizes/async.stdout         |  2 +-
 5 files changed, 34 insertions(+), 16 deletions(-)

diff --git a/compiler/rustc_session/src/code_stats.rs b/compiler/rustc_session/src/code_stats.rs
index 2553df33cc765..93839fa1186af 100644
--- a/compiler/rustc_session/src/code_stats.rs
+++ b/compiler/rustc_session/src/code_stats.rs
@@ -44,6 +44,10 @@ pub struct FieldInfo {
     pub offset: u64,
     pub size: u64,
     pub align: u64,
+    /// Name of the type of this field.
+    /// Present only if the creator thought that this would be important for identifying the field,
+    /// typically because the field name is uninformative.
+    pub type_name: Option<Symbol>,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@@ -192,7 +196,7 @@ impl CodeStats {
                 fields.sort_by_key(|f| (f.offset, f.size));
 
                 for field in fields {
-                    let FieldInfo { kind, ref name, offset, size, align } = field;
+                    let FieldInfo { kind, ref name, offset, size, align, type_name } = field;
 
                     if offset > min_offset {
                         let pad = offset - min_offset;
@@ -201,21 +205,27 @@ impl CodeStats {
 
                     if offset < min_offset {
                         // If this happens it's probably a union.
-                        println!(
+                        print!(
                             "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
                                   offset: {offset} bytes, \
                                   alignment: {align} bytes"
                         );
                     } else if info.packed || offset == min_offset {
-                        println!("print-type-size {indent}{kind} `.{name}`: {size} bytes");
+                        print!("print-type-size {indent}{kind} `.{name}`: {size} bytes");
                     } else {
                         // Include field alignment in output only if it caused padding injection
-                        println!(
+                        print!(
                             "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
                                   alignment: {align} bytes"
                         );
                     }
 
+                    if let Some(type_name) = type_name {
+                        println!(", type: {type_name}");
+                    } else {
+                        println!();
+                    }
+
                     min_offset = offset + size;
                 }
             }
diff --git a/compiler/rustc_ty_utils/src/layout.rs b/compiler/rustc_ty_utils/src/layout.rs
index 48e76d50be169..9c3d39307b26f 100644
--- a/compiler/rustc_ty_utils/src/layout.rs
+++ b/compiler/rustc_ty_utils/src/layout.rs
@@ -10,6 +10,7 @@ use rustc_middle::ty::layout::{
 use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::{self, AdtDef, EarlyBinder, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt};
 use rustc_session::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo};
+use rustc_span::sym;
 use rustc_span::symbol::Symbol;
 use rustc_target::abi::*;
 
@@ -1007,6 +1008,7 @@ fn variant_info_for_adt<'tcx>(
                     offset: offset.bytes(),
                     size: field_layout.size.bytes(),
                     align: field_layout.align.abi.bytes(),
+                    type_name: None,
                 }
             })
             .collect();
@@ -1090,6 +1092,7 @@ fn variant_info_for_coroutine<'tcx>(
                 offset: offset.bytes(),
                 size: field_layout.size.bytes(),
                 align: field_layout.align.abi.bytes(),
+                type_name: None,
             }
         })
         .collect();
@@ -1104,19 +1107,24 @@ fn variant_info_for_coroutine<'tcx>(
                 .iter()
                 .enumerate()
                 .map(|(field_idx, local)| {
+                    let field_name = coroutine.field_names[*local];
                     let field_layout = variant_layout.field(cx, field_idx);
                     let offset = variant_layout.fields.offset(field_idx);
                     // The struct is as large as the last field's end
                     variant_size = variant_size.max(offset + field_layout.size);
                     FieldInfo {
                         kind: FieldKind::CoroutineLocal,
-                        name: coroutine.field_names[*local].unwrap_or(Symbol::intern(&format!(
+                        name: field_name.unwrap_or(Symbol::intern(&format!(
                             ".coroutine_field{}",
                             local.as_usize()
                         ))),
                         offset: offset.bytes(),
                         size: field_layout.size.bytes(),
                         align: field_layout.align.abi.bytes(),
+                        // Include the type name if there is no field name, or if the name is the
+                        // __awaitee placeholder symbol which means a child future being `.await`ed.
+                        type_name: (field_name.is_none() || field_name == Some(sym::__awaitee))
+                            .then(|| Symbol::intern(&field_layout.ty.to_string())),
                     }
                 })
                 .chain(upvar_fields.iter().copied())
diff --git a/tests/ui/async-await/future-sizes/async-awaiting-fut.stdout b/tests/ui/async-await/future-sizes/async-awaiting-fut.stdout
index 47b39e5246dde..d6fb643702c56 100644
--- a/tests/ui/async-await/future-sizes/async-awaiting-fut.stdout
+++ b/tests/ui/async-await/future-sizes/async-awaiting-fut.stdout
@@ -2,7 +2,7 @@ print-type-size type: `{async fn body@$DIR/async-awaiting-fut.rs:21:21: 24:2}`:
 print-type-size     discriminant: 1 bytes
 print-type-size     variant `Unresumed`: 0 bytes
 print-type-size     variant `Suspend0`: 3077 bytes
-print-type-size         local `.__awaitee`: 3077 bytes
+print-type-size         local `.__awaitee`: 3077 bytes, type: {async fn body@$DIR/async-awaiting-fut.rs:10:64: 19:2}
 print-type-size     variant `Returned`: 0 bytes
 print-type-size     variant `Panicked`: 0 bytes
 print-type-size type: `std::mem::ManuallyDrop<{async fn body@$DIR/async-awaiting-fut.rs:10:64: 19:2}>`: 3077 bytes, alignment: 1 bytes
@@ -19,19 +19,19 @@ print-type-size     variant `Suspend0`: 2052 bytes
 print-type-size         upvar `.fut`: 1025 bytes, offset: 0 bytes, alignment: 1 bytes
 print-type-size         padding: 1 bytes
 print-type-size         local `.fut`: 1025 bytes, alignment: 1 bytes
-print-type-size         local `..coroutine_field4`: 1 bytes
-print-type-size         local `.__awaitee`: 1 bytes
+print-type-size         local `..coroutine_field4`: 1 bytes, type: bool
+print-type-size         local `.__awaitee`: 1 bytes, type: {async fn body@$DIR/async-awaiting-fut.rs:6:17: 6:19}
 print-type-size     variant `Suspend1`: 3076 bytes
 print-type-size         upvar `.fut`: 1025 bytes, offset: 0 bytes, alignment: 1 bytes
 print-type-size         padding: 1026 bytes
-print-type-size         local `..coroutine_field4`: 1 bytes, alignment: 1 bytes
-print-type-size         local `.__awaitee`: 1025 bytes
+print-type-size         local `..coroutine_field4`: 1 bytes, alignment: 1 bytes, type: bool
+print-type-size         local `.__awaitee`: 1025 bytes, type: {async fn body@$DIR/async-awaiting-fut.rs:8:35: 8:37}
 print-type-size     variant `Suspend2`: 2052 bytes
 print-type-size         upvar `.fut`: 1025 bytes, offset: 0 bytes, alignment: 1 bytes
 print-type-size         padding: 1 bytes
 print-type-size         local `.fut`: 1025 bytes, alignment: 1 bytes
-print-type-size         local `..coroutine_field4`: 1 bytes
-print-type-size         local `.__awaitee`: 1 bytes
+print-type-size         local `..coroutine_field4`: 1 bytes, type: bool
+print-type-size         local `.__awaitee`: 1 bytes, type: {async fn body@$DIR/async-awaiting-fut.rs:6:17: 6:19}
 print-type-size     variant `Returned`: 1025 bytes
 print-type-size         upvar `.fut`: 1025 bytes, offset: 0 bytes, alignment: 1 bytes
 print-type-size     variant `Panicked`: 1025 bytes
diff --git a/tests/ui/async-await/future-sizes/large-arg.stdout b/tests/ui/async-await/future-sizes/large-arg.stdout
index 005460df626fc..589df102af4bd 100644
--- a/tests/ui/async-await/future-sizes/large-arg.stdout
+++ b/tests/ui/async-await/future-sizes/large-arg.stdout
@@ -2,7 +2,7 @@ print-type-size type: `{async fn body@$DIR/large-arg.rs:6:21: 8:2}`: 3076 bytes,
 print-type-size     discriminant: 1 bytes
 print-type-size     variant `Unresumed`: 0 bytes
 print-type-size     variant `Suspend0`: 3075 bytes
-print-type-size         local `.__awaitee`: 3075 bytes
+print-type-size         local `.__awaitee`: 3075 bytes, type: {async fn body@$DIR/large-arg.rs:10:30: 12:2}
 print-type-size     variant `Returned`: 0 bytes
 print-type-size     variant `Panicked`: 0 bytes
 print-type-size type: `std::mem::ManuallyDrop<{async fn body@$DIR/large-arg.rs:10:30: 12:2}>`: 3075 bytes, alignment: 1 bytes
@@ -17,7 +17,7 @@ print-type-size     variant `Unresumed`: 1024 bytes
 print-type-size         upvar `.t`: 1024 bytes
 print-type-size     variant `Suspend0`: 3074 bytes
 print-type-size         upvar `.t`: 1024 bytes
-print-type-size         local `.__awaitee`: 2050 bytes
+print-type-size         local `.__awaitee`: 2050 bytes, type: {async fn body@$DIR/large-arg.rs:13:26: 15:2}
 print-type-size     variant `Returned`: 1024 bytes
 print-type-size         upvar `.t`: 1024 bytes
 print-type-size     variant `Panicked`: 1024 bytes
@@ -34,7 +34,7 @@ print-type-size     variant `Unresumed`: 1024 bytes
 print-type-size         upvar `.t`: 1024 bytes
 print-type-size     variant `Suspend0`: 2049 bytes
 print-type-size         upvar `.t`: 1024 bytes
-print-type-size         local `.__awaitee`: 1025 bytes
+print-type-size         local `.__awaitee`: 1025 bytes, type: {async fn body@$DIR/large-arg.rs:16:26: 18:2}
 print-type-size     variant `Returned`: 1024 bytes
 print-type-size         upvar `.t`: 1024 bytes
 print-type-size     variant `Panicked`: 1024 bytes
diff --git a/tests/ui/print_type_sizes/async.stdout b/tests/ui/print_type_sizes/async.stdout
index e1be98f85d847..1df4d85d09e33 100644
--- a/tests/ui/print_type_sizes/async.stdout
+++ b/tests/ui/print_type_sizes/async.stdout
@@ -5,7 +5,7 @@ print-type-size         upvar `.arg`: 8192 bytes
 print-type-size     variant `Suspend0`: 16385 bytes
 print-type-size         upvar `.arg`: 8192 bytes
 print-type-size         local `.arg`: 8192 bytes
-print-type-size         local `.__awaitee`: 1 bytes
+print-type-size         local `.__awaitee`: 1 bytes, type: {async fn body@$DIR/async.rs:8:17: 8:19}
 print-type-size     variant `Returned`: 8192 bytes
 print-type-size         upvar `.arg`: 8192 bytes
 print-type-size     variant `Panicked`: 8192 bytes