diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index 94e0b704e6c08..940aae422bf8e 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -1,4 +1,4 @@ -use rustc_ast::{self as ast, EnumDef, MetaItem, Safety}; +use rustc_ast::{self as ast, EnumDef, ExprKind, MetaItem, Safety, TyKind, token}; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_session::config::FmtDebug; use rustc_span::{Ident, Span, Symbol, sym}; @@ -227,6 +227,11 @@ fn show_fieldless_enum( substr: &Substructure<'_>, ) -> BlockOrExpr { let fmt = substr.nonselflike_args[0].clone(); + if let Some((stmts, expr)) = show_fieldless_enum_concat_str(cx, span, def, substr, fmt.clone()) + { + return BlockOrExpr::new_mixed(stmts, Some(expr)); + } + let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]); let arms = def .variants .iter() @@ -247,6 +252,142 @@ fn show_fieldless_enum( }) .collect::>(); let name = cx.expr_match(span, cx.expr_self(span), arms); - let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]); BlockOrExpr::new_expr(cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name])) } + +/// Special case for fieldless enums with no discriminants. Builds +/// ```text +/// impl ::core::fmt::Debug for A { +/// fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { +/// static __NAMES: &str = "ABBBCC"; +/// static __OFFSET: [usize; 4] =[0, 1, 4, 6]; +/// let __d = ::core::intrinsics::discriminant_value(self) as usize; +/// ::core::fmt::Formatter::debug_c_like_enums_write_str(f, __NAMES, &__OFFSET, __d) +/// } +/// } +/// ``` +fn show_fieldless_enum_concat_str( + cx: &ExtCtxt<'_>, + span: Span, + def: &EnumDef, + substr: &Substructure<'_>, + fmt: Box, +) -> Option<(ThinVec, Box)> { + // Minimum variants count where this optimization starts to pay off. + // See https://github.com/rust-lang/rust/pull/155452 for more details. + const THRESHOLD: usize = 10; + let variants_count = def.variants.len(); + if variants_count < THRESHOLD { + return None; + } + + let variant_names = def + .variants + .iter() + .map(|v| v.disr_expr.is_none().then_some(v.ident.name.as_str())) + .collect::>>()?; + + let total_bytes: usize = variant_names.iter().map(|n| n.len()).sum(); + let mut concatenated_names = String::with_capacity(total_bytes); + let mut offset_indices = Vec::with_capacity(variant_names.len() + 1); + offset_indices.push(0); + + for name in variant_names.iter() { + concatenated_names.push_str(name); + offset_indices.push(concatenated_names.len()); + } + + // Create the constant concatenated string + let names_ident = Ident::from_str_and_span("__NAMES", span); + let str_ty = cx.ty( + span, + TyKind::Ref( + None, + ast::MutTy { + ty: cx.ty( + span, + TyKind::Path(None, ast::Path::from_ident(Ident::new(sym::str, span))), + ), + mutbl: ast::Mutability::Not, + }, + ), + ); + let names_str_body = cx.expr_str(span, Symbol::intern(&concatenated_names)); + let names_static_item = + cx.item_static(span, names_ident, str_ty, ast::Mutability::Not, names_str_body); + + // Create the constant offset array + let offset_ident = Ident::from_str_and_span("__OFFSET", span); + let offset_index_exprs = + offset_indices.iter().map(|s| cx.expr_usize(span, *s)).collect::>(); + let starts_array_body = cx.expr_array(span, offset_index_exprs); + let usize_ty = + cx.ty(span, TyKind::Path(None, ast::Path::from_ident(Ident::new(sym::usize, span)))); + let offset_array_len_expr = cx.anon_const( + span, + ExprKind::Lit(token::Lit::new( + token::LitKind::Integer, + Symbol::intern(&(variants_count + 1).to_string()), + None, + )), + ); + let offset_static_item = cx.item_static( + span, + offset_ident, + cx.ty(span, TyKind::Array(usize_ty, offset_array_len_expr)), + ast::Mutability::Not, + starts_array_body, + ); + + let variant_index_ident = Ident::from_str_and_span("__d", span); + let arms = def + .variants + .iter() + .enumerate() + .map(|(i, v)| { + let variant_path = cx.path(span, vec![substr.type_ident, v.ident]); + let pat = match &v.data { + ast::VariantData::Tuple(fields, _) => { + debug_assert!(fields.is_empty()); + cx.pat_tuple_struct(span, variant_path, ThinVec::new()) + } + ast::VariantData::Struct { fields, .. } => { + debug_assert!(fields.is_empty()); + cx.pat_struct(span, variant_path, ThinVec::new()) + } + ast::VariantData::Unit(_) => cx.pat_path(span, variant_path), + }; + cx.arm(span, pat, cx.expr_usize(span, i)) + }) + .collect::>(); + + let variant_index_expr = cx.expr_match(span, cx.expr_self(span), arms); + + let discriminant_let_stmt = cx.stmt_let(span, false, variant_index_ident, variant_index_expr); + + // __d expression + let discriminant_expr = cx.expr_ident(span, variant_index_ident); + + // __NAMES expression + let names_expr = cx.expr_ident(span, names_ident); + + // &__OFFSET expression + let offset_ref_expr = cx.expr_addr_of(span, cx.expr_ident(span, offset_ident)); + + // ::core::fmt::Formatter::debug_c_like_enum_write_str(f, __NAMES, &__OFFSET, __d) + let fn_path = cx.std_path(&[sym::fmt, sym::Formatter, sym::debug_c_like_enum_write_str]); + let call_expr = cx.expr_call_global( + span, + fn_path, + thin_vec![fmt, names_expr, offset_ref_expr, discriminant_expr], + ); + + Some(( + thin_vec![ + cx.stmt_item(span, names_static_item), + cx.stmt_item(span, offset_static_item), + discriminant_let_stmt, + ], + call_expr, + )) +} diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 768f1be1cd48d..3fcb59ea2d701 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -774,6 +774,7 @@ symbols! { debug_assert_macro, debug_assert_ne_macro, debug_assertions, + debug_c_like_enum_write_str, debug_struct_fields_finish, debug_tuple_fields_finish, debugger_visualizer, diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index 43dba18375992..a77910b1475f4 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -2577,6 +2577,21 @@ impl<'a> Formatter<'a> { builder.finish() } + /// Shrinks `derive(Debug)` code, for faster compilation and smaller binaries. + /// For C-like enums with concatenated variant name strings. + #[doc(hidden)] + #[unstable(feature = "fmt_helpers_for_derive", issue = "none")] + pub fn debug_c_like_enum_write_str<'b>( + &'b mut self, + names: &str, + offset: &[usize], + discr: usize, + ) -> Result { + let start = offset[discr]; + let end = offset[discr + 1]; + self.write_str(&names[start..end]) + } + /// Creates a `DebugTuple` builder designed to assist with creation of /// `fmt::Debug` implementations for tuple structs. /// diff --git a/tests/ui/derives/deriving-all-codegen.rs b/tests/ui/derives/deriving-all-codegen.rs index 343d4095da470..a5342c73a5962 100644 --- a/tests/ui/derives/deriving-all-codegen.rs +++ b/tests/ui/derives/deriving-all-codegen.rs @@ -154,6 +154,29 @@ enum Fieldless { C, } +// A C-like, fieldless enum with variants of varying name lengths. +#[derive(Debug)] +enum Fieldless0 { + A, + BBB, + CC, +} + +// A C-like, fieldless enum with 10 variants. +#[derive(Debug)] +enum Fieldless10 { + AAAAA, + BBBB, + CC, + DDDDDDDD, + E, + FFFFFFFFFFFFF, + GGGGGG, + Hatsune, + IIIIIII, + JJJJJJJJJ, +} + // An enum with multiple fieldless and fielded variants. #[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] enum Mixed { diff --git a/tests/ui/derives/deriving-all-codegen.stdout b/tests/ui/derives/deriving-all-codegen.stdout index 320c1b5861162..1a43ec263645f 100644 --- a/tests/ui/derives/deriving-all-codegen.stdout +++ b/tests/ui/derives/deriving-all-codegen.stdout @@ -1243,6 +1243,61 @@ impl ::core::cmp::Ord for Fieldless { } } +// A C-like, fieldless enum with variants of varying name lengths. +enum Fieldless0 { A, BBB, CC, } +#[automatically_derived] +impl ::core::fmt::Debug for Fieldless0 { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::write_str(f, + match self { + Fieldless0::A => "A", + Fieldless0::BBB => "BBB", + Fieldless0::CC => "CC", + }) + } +} + +// A C-like, fieldless enum with 10 variants. +enum Fieldless10 { + AAAAA, + BBBB, + CC, + DDDDDDDD, + E, + FFFFFFFFFFFFF, + GGGGGG, + Hatsune, + IIIIIII, + JJJJJJJJJ, +} +#[automatically_derived] +impl ::core::fmt::Debug for Fieldless10 { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + static __NAMES: &str = + "AAAAABBBBCCDDDDDDDDEFFFFFFFFFFFFFGGGGGGHatsuneIIIIIIIJJJJJJJJJ"; + static __OFFSET: [usize; 11] = + [0usize, 5usize, 9usize, 11usize, 19usize, 20usize, 33usize, + 39usize, 46usize, 53usize, 62usize]; + let __d = + match self { + Fieldless10::AAAAA => 0usize, + Fieldless10::BBBB => 1usize, + Fieldless10::CC => 2usize, + Fieldless10::DDDDDDDD => 3usize, + Fieldless10::E => 4usize, + Fieldless10::FFFFFFFFFFFFF => 5usize, + Fieldless10::GGGGGG => 6usize, + Fieldless10::Hatsune => 7usize, + Fieldless10::IIIIIII => 8usize, + Fieldless10::JJJJJJJJJ => 9usize, + }; + ::core::fmt::Formatter::debug_c_like_enum_write_str(f, __NAMES, + &__OFFSET, __d) + } +} + // An enum with multiple fieldless and fielded variants. enum Mixed {