Skip to content

Commit

Permalink
Reduce amount of code generated by ValueDebugFormat (#8239)
Browse files Browse the repository at this point in the history
I noticed that `ValueDebugFormat` was pretty common near the top of
`cargo llvm-lines`. It looked like this was probably due to the large
number of await points inserted into the code. Each await point
complicates the state machine that rustc must generate and pass to LLVM.

This reduces the number of await points from one per field to one per
struct. There's a minor change in behavior as the child fields are
awaited concurrently (`join_all`) instead of one-at-a-time.

## Why?

Less code generated by macros and passed to LLVM should mean faster
compiles and a smaller binary that we're shipping to the user. The total
change here is pretty small though, so it's probably not worth
benchmarking compile times.

## Testing

```
cargo test -p turbo-tasks-macros-tests
```

## cargo llvm-lines (sanity check)

```
cargo llvm-lines -p next-swc-napi
```

Before:

```
  Lines                  Copies               Function name
  -----                  ------               -------------
  1094257                31851                (TOTAL)
...
     1246 (0.1%,  2.2%)      1 (0.0%,  0.0%)  <next_swc_napi[a7ff9ed323af314d]::app_structure::ComponentsForJs as turbo_tasks[129d614a87aad753]::debug::ValueDebugFormat>::value_debug_format::{closure#0}
```

After:

```
  Lines                  Copies               Function name
  -----                  ------               -------------
  1088974                31851                (TOTAL)
...
      462 (0.0%,  7.7%)      1 (0.0%,  0.3%)  <next_swc_napi[a7ff9ed323af314d]::app_structure::ComponentsForJs as turbo_tasks[129d614a87aad753]::debug::ValueDebugFormat>::value_debug_format::{closure#0}
```

## Binary size (next-swc, stripped arm64 linux tarball)

Before: `188180480 bytes`
After: `187443200 bytes`

<details>
<summary><strong>Example Generated Code (After)</strong>
</summary>

```rust
// =================================================
// Recursive expansion of the ValueDebugFormat macro
// =================================================

impl turbo_tasks::debug::ValueDebugFormat for ComponentsForJs {
    fn value_debug_format<'a>(
        &'a self,
        depth: usize,
    ) -> turbo_tasks::debug::ValueDebugFormatString<'a> {
        turbo_tasks::debug::ValueDebugFormatString::Async(Box::pin(async move {
            if depth == 0 {
                return Ok(stringify!(ComponentsForJs).to_string());
            }
            use turbo_tasks::debug::{internal::*, ValueDebugFormat};
            Ok(format!(
                "{:#?}",
                match self {
                    ComponentsForJs {
                        page,
                        layout,
                        error,
                        loading,
                        template,
                        not_found,
                        default,
                        route,
                        metadata,
                    } =>
                        FormattingStruct::new_named_async(
                            stringify!(ComponentsForJs),
                            vec![
                                AsyncFormattingField::new(
                                    stringify!(page),
                                    turbo_tasks::macro_helpers::value_debug_format_field(
                                        page.value_debug_format(depth.saturating_sub(1))
                                    ),
                                ),
                                AsyncFormattingField::new(
                                    stringify!(layout),
                                    turbo_tasks::macro_helpers::value_debug_format_field(
                                        layout.value_debug_format(depth.saturating_sub(1))
                                    ),
                                ),
                                AsyncFormattingField::new(
                                    stringify!(error),
                                    turbo_tasks::macro_helpers::value_debug_format_field(
                                        error.value_debug_format(depth.saturating_sub(1))
                                    ),
                                ),
                                AsyncFormattingField::new(
                                    stringify!(loading),
                                    turbo_tasks::macro_helpers::value_debug_format_field(
                                        loading.value_debug_format(depth.saturating_sub(1))
                                    ),
                                ),
                                AsyncFormattingField::new(
                                    stringify!(template),
                                    turbo_tasks::macro_helpers::value_debug_format_field(
                                        template.value_debug_format(depth.saturating_sub(1))
                                    ),
                                ),
                                AsyncFormattingField::new(
                                    stringify!(not_found),
                                    turbo_tasks::macro_helpers::value_debug_format_field(
                                        not_found.value_debug_format(depth.saturating_sub(1))
                                    ),
                                ),
                                AsyncFormattingField::new(
                                    stringify!(default),
                                    turbo_tasks::macro_helpers::value_debug_format_field(
                                        default.value_debug_format(depth.saturating_sub(1))
                                    ),
                                ),
                                AsyncFormattingField::new(
                                    stringify!(route),
                                    turbo_tasks::macro_helpers::value_debug_format_field(
                                        route.value_debug_format(depth.saturating_sub(1))
                                    ),
                                ),
                                AsyncFormattingField::new(
                                    stringify!(metadata),
                                    turbo_tasks::macro_helpers::value_debug_format_field(
                                        metadata.value_debug_format(depth.saturating_sub(1))
                                    ),
                                ),
                            ],
                        )
                        .await,
                }
            ))
        }))
    }
}
```
</details>
  • Loading branch information
bgw authored Jun 4, 2024
1 parent bf54c05 commit 67b2a85
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 40 deletions.
30 changes: 25 additions & 5 deletions crates/turbo-tasks-macros-shared/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ use syn::{
pub fn match_expansion<
EN: Fn(&Ident, &FieldsNamed) -> (TokenStream, TokenStream),
EU: Fn(&Ident, &FieldsUnnamed) -> (TokenStream, TokenStream),
U: Fn(&Ident) -> (TokenStream, TokenStream),
U: Fn(&Ident) -> TokenStream,
>(
derive_input: &DeriveInput,
expand_named: &EN,
expand_unnamed: &EU,
expand_unit: &U,
) -> TokenStream {
let ident = &derive_input.ident;
let expand_unit = move |ident| (TokenStream::new(), expand_unit(ident));
match &derive_input.data {
Data::Enum(DataEnum { variants, .. }) => {
let (variants_idents, (variants_fields_capture, expansion)): (
Expand Down Expand Up @@ -63,9 +64,19 @@ pub fn match_expansion<
let (captures, expansion) =
expand_fields(ident, fields, expand_named, expand_unnamed, expand_unit);

quote! {
match self {
#ident #captures => #expansion
if fields.is_empty() {
assert!(captures.is_empty());
// a match expression here doesn't make sense as there's no fields to capture,
// just pass through the inner expression.
expansion
} else {
match fields {
Fields::Named(_) | Fields::Unnamed(_) => quote! {
match self {
#ident #captures => #expansion
}
},
Fields::Unit => unreachable!(),
}
}
}
Expand All @@ -82,6 +93,10 @@ pub fn match_expansion<
}

/// Formats the fields of any structure or enum variant.
///
/// Empty lists of named or unnamed fields are treated as unit structs, as they
/// are semantically identical, and the `expand_unit` codepath can usually
/// generate better code.
pub fn expand_fields<
'ident,
'fields,
Expand All @@ -96,10 +111,15 @@ pub fn expand_fields<
expand_unnamed: EU,
expand_unit: U,
) -> R {
if fields.is_empty() {
// any empty struct (regardless of the syntax used during declaration) is
// equivalent to a unit struct
return expand_unit(ident);
}
match fields {
Fields::Named(named) => expand_named(ident, named),
Fields::Unnamed(unnamed) => expand_unnamed(ident, unnamed),
Fields::Unit => expand_unit(ident),
Fields::Unit => unreachable!(),
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,6 @@ fn hash_unnamed(_ident: &Ident, fields: &FieldsUnnamed) -> (TokenStream2, TokenS
}

/// Hashes a unit struct or enum variant (e.g. `struct Foo;`, `Foo::Bar`).
fn hash_unit(_ident: &Ident) -> (TokenStream2, TokenStream2) {
(quote! {}, quote! { { } })
fn hash_unit(_ident: &Ident) -> TokenStream2 {
quote! { { } }
}
4 changes: 2 additions & 2 deletions crates/turbo-tasks-macros/src/derive/trace_raw_vcs_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@ fn trace_unnamed(_ident: &Ident, fields: &FieldsUnnamed) -> (TokenStream2, Token
)
}

fn trace_unit(_ident: &Ident) -> (TokenStream2, TokenStream2) {
(quote! {}, quote! { { } })
fn trace_unit(_ident: &Ident) -> TokenStream2 {
quote! { { } }
}
73 changes: 43 additions & 30 deletions crates/turbo-tasks-macros/src/derive/value_debug_format_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,35 @@ pub fn derive_value_debug_format(input: TokenStream) -> TokenStream {
/// Formats a single field nested inside named or unnamed fields.
fn format_field(value: TokenStream2) -> TokenStream2 {
quote! {
turbo_tasks::macro_helpers::value_debug_format_field(#value.value_debug_format(depth.saturating_sub(1))).await
turbo_tasks::macro_helpers::value_debug_format_field(#value.value_debug_format(depth.saturating_sub(1)))
}
}

/// Formats a struct or enum variant with named fields (e.g. `struct Foo {
/// bar: u32 }`, `Foo::Bar { baz: u32 }`).
fn format_named(ident: &Ident, fields: &FieldsNamed) -> (TokenStream2, TokenStream2) {
let (captures, fields_idents) = generate_destructuring(fields.named.iter(), &filter_field);
let fields_values = fields_idents.iter().cloned().map(format_field);
(
captures,
quote! {
FormattingStruct::new_named(
stringify!(#ident),
vec![#(
FormattingField::new(
stringify!(#fields_idents),
#fields_values,
),
)*],
)
if fields_idents.is_empty() {
// this can happen if all fields are ignored, we must special-case this to avoid
// rustc being unable to infer the type of an empty vec of futures
quote! {
FormattingStruct::new_named(stringify!(#ident), vec![])
}
} else {
let fields_values = fields_idents.iter().cloned().map(format_field);
quote! {
FormattingStruct::new_named_async(
stringify!(#ident),
vec![#(
AsyncFormattingField::new(
stringify!(#fields_idents),
#fields_values,
),
)*],
).await
}
},
)
}
Expand All @@ -85,29 +93,34 @@ fn format_named(ident: &Ident, fields: &FieldsNamed) -> (TokenStream2, TokenStre
/// Foo(u32)`, `Foo::Bar(u32)`).
fn format_unnamed(ident: &Ident, fields: &FieldsUnnamed) -> (TokenStream2, TokenStream2) {
let (captures, fields_idents) = generate_destructuring(fields.unnamed.iter(), &filter_field);
let fields_values = fields_idents.into_iter().map(format_field);
(
captures,
quote! {
FormattingStruct::new_unnamed(
stringify!(#ident),
vec![#(
#fields_values,
)*],
)
if fields_idents.is_empty() {
// this can happen if all fields are ignored, we must special-case this to avoid
// rustc being unable to infer the type of an empty vec of futures
quote! {
FormattingStruct::new_unnamed(stringify!(#ident), vec![])
}
} else {
let fields_values = fields_idents.into_iter().map(format_field);
quote! {
FormattingStruct::new_unnamed_async(
stringify!(#ident),
vec![#(
#fields_values,
)*],
).await
}
},
)
}

/// Formats a unit struct or enum variant (e.g. `struct Foo;`, `Foo::Bar`).
fn format_unit(ident: &Ident) -> (TokenStream2, TokenStream2) {
(
quote! {},
quote! {
FormattingStruct::new_unnamed(
stringify!(#ident),
vec![],
)
},
)
fn format_unit(ident: &Ident) -> TokenStream2 {
quote! {
FormattingStruct::new_unnamed(
stringify!(#ident),
vec![],
)
}
}
51 changes: 50 additions & 1 deletion crates/turbo-tasks/src/debug/internal.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::borrow::Cow;
use std::{borrow::Cow, future::Future};

use futures::future::join_all;
pub use turbo_tasks_macros::ValueDebug;

/// Representation of a named field of a structure for formatting purposes of
Expand All @@ -16,6 +17,34 @@ impl<'a> FormattingField<'a> {
}
}

/// Representation of a named field of a structure for formatting purposes of
/// `ValueDebug` implementations.
#[derive(Debug)]
pub struct AsyncFormattingField<'a, Fut>
where
Fut: Future<Output = String>,
{
name: &'a str,
contents: Fut,
}

impl<'a, Fut: Future<Output = String>> AsyncFormattingField<'a, Fut>
where
Fut: Future<Output = String>,
{
pub fn new(name: &'a str, contents: Fut) -> Self {
Self { name, contents }
}

pub async fn resolve(self) -> FormattingField<'a> {
let Self { name, contents } = self;
FormattingField {
name,
contents: contents.await,
}
}
}

/// Representation of a structure for formatting purposes of `ValueDebug`
/// implementations.
pub enum FormattingStruct<'a> {
Expand All @@ -36,6 +65,26 @@ impl<'a> FormattingStruct<'a> {
pub fn new_unnamed(name: &'a str, fields: Vec<String>) -> Self {
Self::Unnamed { name, fields }
}

pub async fn new_named_async(
name: &'a str,
fields: Vec<AsyncFormattingField<'a, impl Future<Output = String>>>,
) -> Self {
Self::Named {
name,
fields: join_all(fields.into_iter().map(AsyncFormattingField::resolve)).await,
}
}

pub async fn new_unnamed_async(
name: &'a str,
fields: Vec<impl Future<Output = String>>,
) -> Self {
Self::Unnamed {
name,
fields: join_all(fields).await,
}
}
}

impl<'a> std::fmt::Debug for FormattingStruct<'a> {
Expand Down

0 comments on commit 67b2a85

Please sign in to comment.