diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index 2436800d0099d..59b50d8ba969d 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}; @@ -230,6 +230,10 @@ 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, 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() @@ -250,6 +254,128 @@ 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 { +/// const __NAMES: &str = "ABBBCC"; +/// const __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, + 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_const_item = + cx.item_const(span, names_ident, str_ty, Some(names_str_body), ast::ConstItemKind::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_const_item = cx.item_const( + span, + offset_ident, + cx.ty(span, TyKind::Array(usize_ty, offset_array_len_expr)), + Some(starts_array_body), + ast::ConstItemKind::Body, + ); + + // let __d = ::core::intrinsics::discriminant_value(self) as usize; + let discriminant_ident = Ident::from_str_and_span("__d", span); + let discriminant_intrinsic_path = cx.std_path(&[sym::intrinsics, sym::discriminant_value]); + let discriminant_cast_expr = cx.expr( + span, + ast::ExprKind::Cast( + cx.expr_call_global(span, discriminant_intrinsic_path, thin_vec![cx.expr_self(span)]), + cx.ty_path(ast::Path::from_ident(Ident::new(sym::usize, span))), + ), + ); + let discriminant_let_stmt = + cx.stmt_let(span, false, discriminant_ident, discriminant_cast_expr); + + // __d expression + let discriminant_expr = cx.expr_ident(span, discriminant_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_const_item), + cx.stmt_item(span, offset_const_item), + discriminant_let_stmt, + ], + call_expr, + )) +} diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 95421ba8cfdab..cf603ec0fc86d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -771,6 +771,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 4d000691b8b5f..e5d3ccb027b70 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..6b72b08aefa5a 100644 --- a/tests/ui/derives/deriving-all-codegen.stdout +++ b/tests/ui/derives/deriving-all-codegen.stdout @@ -1243,6 +1243,49 @@ 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 { + const __NAMES: &str = + "AAAAABBBBCCDDDDDDDDEFFFFFFFFFFFFFGGGGGGHatsuneIIIIIIIJJJJJJJJJ"; + const __OFFSET: [usize; 11] = + [0usize, 5usize, 9usize, 11usize, 19usize, 20usize, 33usize, + 39usize, 46usize, 53usize, 62usize]; + let __d = ::core::intrinsics::discriminant_value(self) as usize; + ::core::fmt::Formatter::debug_c_like_enum_write_str(f, __NAMES, + &__OFFSET, __d) + } +} + // An enum with multiple fieldless and fielded variants. enum Mixed {