rust-lang/rust · #162535

Reland debug impl for fieldless enums with concatenated string without using discriminant_value

panstromek · merged Oct 22, 20266 files · 200 + / 0 −
compiler/rustc_builtin_macros/src/deriving/debug.rs94 + / 0 −
@@ -232,6 +232,9 @@ fn show_fieldless_enum(     def: &EnumDef,     type_ident: Ident, ) -> BlockOrExpr {+    if let Some(expr) = show_fieldless_enum_concat_str(cx, span, def, type_ident) {+        return BlockOrExpr::new_expr(expr);+    }     let fmt = cx.expr_name(span, sym::f);     let arms = def         .variants@@ -256,3 +259,94 @@ fn show_fieldless_enum(     let fn_path_write_str = cx.std_path(span, &[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 large enums with no fields. Builds:+/// ```text+/// impl ::core::fmt::Debug for A {+///     fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {+///         ::core::fmt::Formatter::debug_c_like_enum_write_str(f,+///             "A B C D E F",+///             match self {+///                 A::A => 0usize,+///                 A::B => 2usize,+///                 A::C => 4usize,+///                 A::D => 6usize,+///                 A::E => 8usize,+///                 A::F => 10usize,+///             })+///     }+/// }+/// ```+fn show_fieldless_enum_concat_str(+    cx: &ExtCtxt<'_>,+    span: Span,+    def: &EnumDef,+    type_ident: Ident,+) -> Option<Box<ast::Expr>> {+    // Minimum variant count where this optimization starts to pay off.+    // Initial value of 10 came from https://github.com/rust-lang/rust/pull/155452.+    // Refined with the new impl in https://github.com/rust-lang/rust/pull/162535.+    // Tested with thresholds 10,5,2 on rustc-perf+    // and by comparing MIR sizes for various enums in GodBolt.+    //+    // `match` with strings generates:+    //  - a local for each arm+    //  - a basic block with 5 statements for each arm,+    //+    // `match` with integers to index a concatenated string generates+    //  - a single local for the string+    //  - a single local for the index+    //  - a basic block with 2 statements for each arm+    //+    // This reduces borrowck costs on larger enums.+    const THRESHOLD: usize = 5;+    let variants_count = def.variants.len();+    if variants_count < THRESHOLD {+        return None;+    }+    let fmt = cx.expr_name(span, sym::f);++    let mut concatenated_names = String::new();++    let arms = def+        .variants+        .iter()+        .enumerate()+        .map(|(i, v)| {+            let name_offset = concatenated_names.len();++            let name = v.ident.name.as_str();+            concatenated_names.push_str(name);++            let is_last = i == def.variants.len() - 1;+            if !is_last {+                concatenated_names.push(' ');+            }++            let variant_path = cx.path(span, &[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, name_offset))+        })+        .collect::<ThinVec<_>>();++    let names_str_body = cx.expr_str(span, Symbol::intern(&concatenated_names));++    let variant_index_expr = cx.expr_match(span, cx.expr_self(span), arms);++    let fn_path = cx.std_path(span, &[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_str_body, variant_index_expr]);++    Some(call_expr)+}
compiler/rustc_span/src/symbol.rs1 + / 0 −
@@ -804,6 +804,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,
library/core/src/fmt/mod.rs13 + / 0 −
@@ -2619,6 +2619,19 @@ 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.+    /// Names should be separated by space.+    #[doc(hidden)]+    #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]+    pub fn debug_c_like_enum_write_str<'b>(+        &'b mut self,+        names: &str,+        start_index: usize,+    ) -> Result {+        self.write_str(names[start_index..].split(' ').next().unwrap())+    }+     /// Creates a `DebugTuple` builder designed to assist with creation of     /// `fmt::Debug` implementations for tuple structs.     ///
tests/ui/derives/debug/derive-Debug-enum-variants.rs20 + / 0 −
@@ -13,6 +13,17 @@ enum Bar {     D, } +#[derive(Debug)]+enum Large {+    A,+    Bar,+    C,+    Distance,+    Emily,+    F,+    G,+}+ pub fn main() {     // Test variant with data     let foo_a = Foo::A(22);@@ -27,4 +38,13 @@ pub fn main() {      // Test unit variant from different enum     assert_eq!("D".to_string(), format!("{:?}", Bar::D));++    // Test large c-like enum, those use a different implementation.+    assert_eq!("A".to_string(), format!("{:?}", Large::A));+    assert_eq!("Bar".to_string(), format!("{:?}", Large::Bar));+    assert_eq!("C".to_string(), format!("{:?}", Large::C));+    assert_eq!("Distance".to_string(), format!("{:?}", Large::Distance));+    assert_eq!("Emily".to_string(), format!("{:?}", Large::Emily));+    assert_eq!("F".to_string(), format!("{:?}", Large::F));+    assert_eq!("G".to_string(), format!("{:?}", Large::G)); }
tests/ui/derives/deriving-all-codegen.rs23 + / 0 −
@@ -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 {
tests/ui/derives/deriving-all-codegen.stdout49 + / 0 −
@@ -1219,6 +1219,55 @@ 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 {+        ::core::fmt::Formatter::debug_c_like_enum_write_str(f,+            "AAAAA BBBB CC DDDDDDDD E FFFFFFFFFFFFF GGGGGG Hatsune IIIIIII JJJJJJJJJ",+            match self {+                Fieldless10::AAAAA => 0usize,+                Fieldless10::BBBB => 6usize,+                Fieldless10::CC => 11usize,+                Fieldless10::DDDDDDDD => 14usize,+                Fieldless10::E => 23usize,+                Fieldless10::FFFFFFFFFFFFF => 25usize,+                Fieldless10::GGGGGG => 39usize,+                Fieldless10::Hatsune => 46usize,+                Fieldless10::IIIIIII => 54usize,+                Fieldless10::JJJJJJJJJ => 62usize,+            })+    }+}+ // An enum with multiple fieldless and fielded variants. enum Mixed {