diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs
index ecf70da6d96c5..1fffd6f9727d7 100644
--- a/compiler/rustc_builtin_macros/src/deriving/debug.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs
@@ -3,14 +3,10 @@ use crate::deriving::generic::*;
 use crate::deriving::path_std;
 
 use rustc_ast::ptr::P;
-use rustc_ast::{self as ast, Expr, LocalKind, MetaItem};
+use rustc_ast::{self as ast, Expr, MetaItem};
 use rustc_expand::base::{Annotatable, ExtCtxt};
-use rustc_span::symbol::{sym, Ident};
-use rustc_span::{Span, DUMMY_SP};
-
-fn make_mut_borrow(cx: &mut ExtCtxt<'_>, sp: Span, expr: P<Expr>) -> P<Expr> {
-    cx.expr(sp, ast::ExprKind::AddrOf(ast::BorrowKind::Ref, ast::Mutability::Mut, expr))
-}
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::Span;
 
 pub fn expand_deriving_debug(
     cx: &mut ExtCtxt<'_>,
@@ -49,11 +45,7 @@ pub fn expand_deriving_debug(
     trait_def.expand(cx, mitem, item, push)
 }
 
-/// We use the debug builders to do the heavy lifting here
 fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> P<Expr> {
-    // build fmt.debug_struct(<name>).field(<fieldname>, &<fieldval>)....build()
-    // or fmt.debug_tuple(<name>).field(&<fieldval>)....build()
-    // based on the "shape".
     let (ident, vdata, fields) = match substr.fields {
         Struct(vdata, fields) => (substr.type_ident, *vdata, fields),
         EnumMatching(_, _, v, fields) => (v.ident, &v.data, fields),
@@ -67,93 +59,130 @@ fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>
     let name = cx.expr_lit(span, ast::LitKind::Str(ident.name, ast::StrStyle::Cooked));
     let fmt = substr.nonself_args[0].clone();
 
-    // Special fast path for unit variants. In the common case of an enum that is entirely unit
-    // variants (i.e. a C-like enum), this fast path allows LLVM to eliminate the entire switch in
-    // favor of a lookup table.
-    if let ast::VariantData::Unit(..) = vdata {
-        let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
-        let expr = cx.expr_call_global(span, fn_path_write_str, vec![fmt, name]);
-        let stmts = vec![cx.stmt_expr(expr)];
-        let block = cx.block(span, stmts);
-        return cx.expr_block(block);
-    }
-
-    let builder = Ident::new(sym::debug_trait_builder, span);
-    let builder_expr = cx.expr_ident(span, builder);
-
-    let mut stmts = Vec::with_capacity(fields.len() + 2);
-    let fn_path_finish;
-    match vdata {
+    // Struct and tuples are similar enough that we use the same code for both,
+    // with some extra pieces for structs due to the field names.
+    let (is_struct, args_per_field) = match vdata {
         ast::VariantData::Unit(..) => {
-            cx.span_bug(span, "unit variants should have been handled above");
+            // Special fast path for unit variants.
+            //let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
+            //return cx.expr_call_global(span, fn_path_write_str, vec![fmt, name]);
+            assert!(fields.is_empty());
+            (false, 0)
         }
-        ast::VariantData::Tuple(..) => {
-            // tuple struct/"normal" variant
-            let fn_path_debug_tuple = cx.std_path(&[sym::fmt, sym::Formatter, sym::debug_tuple]);
-            let expr = cx.expr_call_global(span, fn_path_debug_tuple, vec![fmt, name]);
-            let expr = make_mut_borrow(cx, span, expr);
-            stmts.push(cx.stmt_let(span, false, builder, expr));
-
-            for field in fields {
-                // Use double indirection to make sure this works for unsized types
-                let field = cx.expr_addr_of(field.span, field.self_.clone());
-                let field = cx.expr_addr_of(field.span, field);
-
-                let fn_path_field = cx.std_path(&[sym::fmt, sym::DebugTuple, sym::field]);
-                let expr =
-                    cx.expr_call_global(span, fn_path_field, vec![builder_expr.clone(), field]);
-
-                // Use `let _ = expr;` to avoid triggering the
-                // unused_results lint.
-                stmts.push(stmt_let_underscore(cx, span, expr));
-            }
+        ast::VariantData::Tuple(..) => (false, 1),
+        ast::VariantData::Struct(..) => (true, 2),
+    };
 
-            fn_path_finish = cx.std_path(&[sym::fmt, sym::DebugTuple, sym::finish]);
-        }
-        ast::VariantData::Struct(..) => {
-            // normal struct/struct variant
-            let fn_path_debug_struct = cx.std_path(&[sym::fmt, sym::Formatter, sym::debug_struct]);
-            let expr = cx.expr_call_global(span, fn_path_debug_struct, vec![fmt, name]);
-            let expr = make_mut_borrow(cx, span, expr);
-            stmts.push(cx.stmt_let(DUMMY_SP, false, builder, expr));
-
-            for field in fields {
+    // The number of fields that can be handled without an array.
+    const CUTOFF: usize = 5;
+
+    if fields.is_empty() {
+        // Special case for no fields.
+        let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
+        cx.expr_call_global(span, fn_path_write_str, vec![fmt, name])
+    } else if fields.len() <= CUTOFF {
+        // Few enough fields that we can use a specific-length method.
+        let debug = if is_struct {
+            format!("debug_struct_field{}_finish", fields.len())
+        } else {
+            format!("debug_tuple_field{}_finish", fields.len())
+        };
+        let fn_path_debug = cx.std_path(&[sym::fmt, sym::Formatter, Symbol::intern(&debug)]);
+
+        let mut args = Vec::with_capacity(2 + fields.len() * args_per_field);
+        args.extend([fmt, name]);
+        for i in 0..fields.len() {
+            let field = &fields[i];
+            if is_struct {
                 let name = cx.expr_lit(
                     field.span,
                     ast::LitKind::Str(field.name.unwrap().name, ast::StrStyle::Cooked),
                 );
-
-                // Use double indirection to make sure this works for unsized types
-                let fn_path_field = cx.std_path(&[sym::fmt, sym::DebugStruct, sym::field]);
-                let field = cx.expr_addr_of(field.span, field.self_.clone());
-                let field = cx.expr_addr_of(field.span, field);
-                let expr = cx.expr_call_global(
-                    span,
-                    fn_path_field,
-                    vec![builder_expr.clone(), name, field],
-                );
-                stmts.push(stmt_let_underscore(cx, span, expr));
+                args.push(name);
             }
-            fn_path_finish = cx.std_path(&[sym::fmt, sym::DebugStruct, sym::finish]);
+            // Use double indirection to make sure this works for unsized types
+            let field = cx.expr_addr_of(field.span, field.self_.clone());
+            let field = cx.expr_addr_of(field.span, field);
+            args.push(field);
         }
-    }
+        cx.expr_call_global(span, fn_path_debug, args)
+    } else {
+        // Enough fields that we must use the any-length method.
+        let mut name_exprs = Vec::with_capacity(fields.len());
+        let mut value_exprs = Vec::with_capacity(fields.len());
+
+        for field in fields {
+            if is_struct {
+                name_exprs.push(cx.expr_lit(
+                    field.span,
+                    ast::LitKind::Str(field.name.unwrap().name, ast::StrStyle::Cooked),
+                ));
+            }
 
-    let expr = cx.expr_call_global(span, fn_path_finish, vec![builder_expr]);
+            // Use double indirection to make sure this works for unsized types
+            let value_ref = cx.expr_addr_of(field.span, field.self_.clone());
+            value_exprs.push(cx.expr_addr_of(field.span, value_ref));
+        }
 
-    stmts.push(cx.stmt_expr(expr));
-    let block = cx.block(span, stmts);
-    cx.expr_block(block)
-}
+        // `let names: &'static _ = &["field1", "field2"];`
+        let names_let = if is_struct {
+            let lt_static = Some(cx.lifetime_static(span));
+            let ty_static_ref =
+                cx.ty_rptr(span, cx.ty_infer(span), lt_static, ast::Mutability::Not);
+            Some(cx.stmt_let_ty(
+                span,
+                false,
+                Ident::new(sym::names, span),
+                Some(ty_static_ref),
+                cx.expr_array_ref(span, name_exprs),
+            ))
+        } else {
+            None
+        };
+
+        // `let values: &[&dyn Debug] = &[&&self.field1, &&self.field2];`
+        let path_debug = cx.path_global(span, cx.std_path(&[sym::fmt, sym::Debug]));
+        let ty_dyn_debug = cx.ty(
+            span,
+            ast::TyKind::TraitObject(vec![cx.trait_bound(path_debug)], ast::TraitObjectSyntax::Dyn),
+        );
+        let ty_slice = cx.ty(
+            span,
+            ast::TyKind::Slice(cx.ty_rptr(span, ty_dyn_debug, None, ast::Mutability::Not)),
+        );
+        let values_let = cx.stmt_let_ty(
+            span,
+            false,
+            Ident::new(sym::values, span),
+            Some(cx.ty_rptr(span, ty_slice, None, ast::Mutability::Not)),
+            cx.expr_array_ref(span, value_exprs),
+        );
+
+        // `fmt::Formatter::debug_struct_fields_finish(fmt, name, names, values)` or
+        // `fmt::Formatter::debug_tuple_fields_finish(fmt, name, values)`
+        let sym_debug = if is_struct {
+            sym::debug_struct_fields_finish
+        } else {
+            sym::debug_tuple_fields_finish
+        };
+        let fn_path_debug_internal = cx.std_path(&[sym::fmt, sym::Formatter, sym_debug]);
+
+        let mut args = Vec::with_capacity(4);
+        args.push(fmt);
+        args.push(name);
+        if is_struct {
+            args.push(cx.expr_ident(span, Ident::new(sym::names, span)));
+        }
+        args.push(cx.expr_ident(span, Ident::new(sym::values, span)));
+        let expr = cx.expr_call_global(span, fn_path_debug_internal, args);
 
-fn stmt_let_underscore(cx: &mut ExtCtxt<'_>, sp: Span, expr: P<ast::Expr>) -> ast::Stmt {
-    let local = P(ast::Local {
-        pat: cx.pat_wild(sp),
-        ty: None,
-        id: ast::DUMMY_NODE_ID,
-        kind: LocalKind::Init(expr),
-        span: sp,
-        attrs: ast::AttrVec::new(),
-        tokens: None,
-    });
-    ast::Stmt { id: ast::DUMMY_NODE_ID, kind: ast::StmtKind::Local(local), span: sp }
+        let mut stmts = Vec::with_capacity(3);
+        if is_struct {
+            stmts.push(names_let.unwrap());
+        }
+        stmts.push(values_let);
+        stmts.push(cx.stmt_expr(expr));
+
+        cx.expr_block(cx.block(span, stmts))
+    }
 }
diff --git a/compiler/rustc_builtin_macros/src/deriving/decodable.rs b/compiler/rustc_builtin_macros/src/deriving/decodable.rs
index 1d892b20729d5..b39f35a9d4056 100644
--- a/compiler/rustc_builtin_macros/src/deriving/decodable.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/decodable.rs
@@ -162,14 +162,13 @@ fn decodable_substructure(
                 cx.expr_match(trait_span, cx.expr_ident(trait_span, variant), arms),
             );
             let lambda = cx.lambda(trait_span, vec![blkarg, variant], result);
-            let variant_vec = cx.expr_vec(trait_span, variants);
-            let variant_vec = cx.expr_addr_of(trait_span, variant_vec);
+            let variant_array_ref = cx.expr_array_ref(trait_span, variants);
             let fn_read_enum_variant_path: Vec<_> =
                 cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_enum_variant]);
             let result = cx.expr_call_global(
                 trait_span,
                 fn_read_enum_variant_path,
-                vec![blkdecoder, variant_vec, lambda],
+                vec![blkdecoder, variant_array_ref, lambda],
             );
             let fn_read_enum_path: Vec<_> =
                 cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_enum]);
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
index 10348c4967c7c..6c2ac34354441 100644
--- a/compiler/rustc_builtin_macros/src/format.rs
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -776,7 +776,7 @@ impl<'a, 'b> Context<'a, 'b> {
 
         // First, build up the static array which will become our precompiled
         // format "string"
-        let pieces = self.ecx.expr_vec_slice(self.fmtsp, self.str_pieces);
+        let pieces = self.ecx.expr_array_ref(self.fmtsp, self.str_pieces);
 
         // We need to construct a &[ArgumentV1] to pass into the fmt::Arguments
         // constructor. In general the expressions in this slice might be
@@ -849,7 +849,7 @@ impl<'a, 'b> Context<'a, 'b> {
             fmt_args.push(Context::format_arg(self.ecx, self.macsp, span, arg_ty, arg));
         }
 
-        let args_array = self.ecx.expr_vec(self.macsp, fmt_args);
+        let args_array = self.ecx.expr_array(self.macsp, fmt_args);
         let args_slice = self.ecx.expr_addr_of(
             self.macsp,
             if no_need_for_match {
@@ -879,7 +879,7 @@ impl<'a, 'b> Context<'a, 'b> {
         } else {
             // Build up the static array which will store our precompiled
             // nonstandard placeholders, if there are any.
-            let fmt = self.ecx.expr_vec_slice(self.macsp, self.pieces);
+            let fmt = self.ecx.expr_array_ref(self.macsp, self.pieces);
 
             let path = self.ecx.std_path(&[sym::fmt, sym::UnsafeArg, sym::new]);
             let unsafe_arg = self.ecx.expr_call_global(self.macsp, path, Vec::new());
diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
index 03159d4395066..5cfda33491d5b 100644
--- a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
+++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
@@ -317,7 +317,7 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
                         proc_macro_ty_method_path(cx, custom_derive),
                         vec![
                             cx.expr_str(span, cd.trait_name),
-                            cx.expr_vec_slice(
+                            cx.expr_array_ref(
                                 span,
                                 cd.attrs.iter().map(|&s| cx.expr_str(span, s)).collect::<Vec<_>>(),
                             ),
@@ -362,7 +362,7 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
                 ast::Mutability::Not,
             ),
             ast::Mutability::Not,
-            cx.expr_vec_slice(span, decls),
+            cx.expr_array_ref(span, decls),
         )
         .map(|mut i| {
             let attr = cx.meta_word(span, sym::rustc_proc_macro_decls);
diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs
index db8dce804a31b..27f6ef5877a16 100644
--- a/compiler/rustc_builtin_macros/src/test_harness.rs
+++ b/compiler/rustc_builtin_macros/src/test_harness.rs
@@ -351,7 +351,7 @@ fn mk_tests_slice(cx: &TestCtxt<'_>, sp: Span) -> P<ast::Expr> {
     debug!("building test vector from {} tests", cx.test_cases.len());
     let ecx = &cx.ext_cx;
 
-    ecx.expr_vec_slice(
+    ecx.expr_array_ref(
         sp,
         cx.test_cases
             .iter()
diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs
index e73c31c98fe32..1694a8865dde2 100644
--- a/compiler/rustc_expand/src/build.rs
+++ b/compiler/rustc_expand/src/build.rs
@@ -57,6 +57,10 @@ impl<'a> ExtCtxt<'a> {
         P(ast::Ty { id: ast::DUMMY_NODE_ID, span, kind, tokens: None })
     }
 
+    pub fn ty_infer(&self, span: Span) -> P<ast::Ty> {
+        self.ty(span, ast::TyKind::Infer)
+    }
+
     pub fn ty_path(&self, path: ast::Path) -> P<ast::Ty> {
         self.ty(path.span, ast::TyKind::Path(None, path))
     }
@@ -140,11 +144,26 @@ impl<'a> ExtCtxt<'a> {
         ast::Lifetime { id: ast::DUMMY_NODE_ID, ident: ident.with_span_pos(span) }
     }
 
+    pub fn lifetime_static(&self, span: Span) -> ast::Lifetime {
+        self.lifetime(span, Ident::new(kw::StaticLifetime, span))
+    }
+
     pub fn stmt_expr(&self, expr: P<ast::Expr>) -> ast::Stmt {
         ast::Stmt { id: ast::DUMMY_NODE_ID, span: expr.span, kind: ast::StmtKind::Expr(expr) }
     }
 
     pub fn stmt_let(&self, sp: Span, mutbl: bool, ident: Ident, ex: P<ast::Expr>) -> ast::Stmt {
+        self.stmt_let_ty(sp, mutbl, ident, None, ex)
+    }
+
+    pub fn stmt_let_ty(
+        &self,
+        sp: Span,
+        mutbl: bool,
+        ident: Ident,
+        ty: Option<P<ast::Ty>>,
+        ex: P<ast::Expr>,
+    ) -> ast::Stmt {
         let pat = if mutbl {
             let binding_mode = ast::BindingMode::ByValue(ast::Mutability::Mut);
             self.pat_ident_binding_mode(sp, ident, binding_mode)
@@ -153,7 +172,7 @@ impl<'a> ExtCtxt<'a> {
         };
         let local = P(ast::Local {
             pat,
-            ty: None,
+            ty,
             id: ast::DUMMY_NODE_ID,
             kind: LocalKind::Init(ex),
             span: sp,
@@ -315,12 +334,16 @@ impl<'a> ExtCtxt<'a> {
         self.expr_lit(sp, ast::LitKind::Bool(value))
     }
 
-    pub fn expr_vec(&self, sp: Span, exprs: Vec<P<ast::Expr>>) -> P<ast::Expr> {
+    /// `[expr1, expr2, ...]`
+    pub fn expr_array(&self, sp: Span, exprs: Vec<P<ast::Expr>>) -> P<ast::Expr> {
         self.expr(sp, ast::ExprKind::Array(exprs))
     }
-    pub fn expr_vec_slice(&self, sp: Span, exprs: Vec<P<ast::Expr>>) -> P<ast::Expr> {
-        self.expr_addr_of(sp, self.expr_vec(sp, exprs))
+
+    /// `&[expr1, expr2, ...]`
+    pub fn expr_array_ref(&self, sp: Span, exprs: Vec<P<ast::Expr>>) -> P<ast::Expr> {
+        self.expr_addr_of(sp, self.expr_array(sp, exprs))
     }
+
     pub fn expr_str(&self, sp: Span, s: Symbol) -> P<ast::Expr> {
         self.expr_lit(sp, ast::LitKind::Str(s, ast::StrStyle::Cooked))
     }
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 8a6941a451621..48766c6791096 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -567,8 +567,10 @@ symbols! {
         debug_assert_ne_macro,
         debug_assertions,
         debug_struct,
+        debug_struct_fields_finish,
         debug_trait_builder,
         debug_tuple,
+        debug_tuple_fields_finish,
         debugger_visualizer,
         decl_macro,
         declare_lint_pass,
diff --git a/compiler/rustc_type_ir/src/lib.rs b/compiler/rustc_type_ir/src/lib.rs
index cb1602816ae5e..f9708d6d9195f 100644
--- a/compiler/rustc_type_ir/src/lib.rs
+++ b/compiler/rustc_type_ir/src/lib.rs
@@ -1,3 +1,4 @@
+#![feature(fmt_helpers_for_derive)]
 #![feature(min_specialization)]
 #![feature(rustc_attrs)]
 
diff --git a/compiler/rustc_type_ir/src/sty.rs b/compiler/rustc_type_ir/src/sty.rs
index 5cd2901324a2e..74737e30bb4d3 100644
--- a/compiler/rustc_type_ir/src/sty.rs
+++ b/compiler/rustc_type_ir/src/sty.rs
@@ -554,137 +554,37 @@ impl<I: Interner> hash::Hash for TyKind<I> {
 // This is manually implemented because a derive would require `I: Debug`
 impl<I: Interner> fmt::Debug for TyKind<I> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match (&*self,) {
-            (&Bool,) => fmt::Formatter::write_str(f, "Bool"),
-            (&Char,) => fmt::Formatter::write_str(f, "Char"),
-            (&Int(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Int");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Uint(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Uint");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Float(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Float");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Adt(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Adt");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Foreign(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Foreign");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Str,) => fmt::Formatter::write_str(f, "Str"),
-            (&Array(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Array");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Slice(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Slice");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&RawPtr(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "RawPtr");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Ref(ref __self_0, ref __self_1, ref __self_2),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Ref");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_2);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&FnDef(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "FnDef");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&FnPtr(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "FnPtr");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Dynamic(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Dynamic");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Closure(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Closure");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Generator(ref __self_0, ref __self_1, ref __self_2),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Generator");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_2);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&GeneratorWitness(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "GeneratorWitness");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Never,) => fmt::Formatter::write_str(f, "Never"),
-            (&Tuple(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Tuple");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Projection(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Projection");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Opaque(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Opaque");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Param(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Param");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Bound(ref __self_0, ref __self_1),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Bound");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_1);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Placeholder(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Placeholder");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Infer(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Infer");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
-            (&Error(ref __self_0),) => {
-                let debug_trait_builder = &mut fmt::Formatter::debug_tuple(f, "Error");
-                let _ = fmt::DebugTuple::field(debug_trait_builder, &__self_0);
-                fmt::DebugTuple::finish(debug_trait_builder)
-            }
+        use std::fmt::*;
+        match self {
+            Bool => Formatter::write_str(f, "Bool"),
+            Char => Formatter::write_str(f, "Char"),
+            Int(f0) => Formatter::debug_tuple_field1_finish(f, "Int", f0),
+            Uint(f0) => Formatter::debug_tuple_field1_finish(f, "Uint", f0),
+            Float(f0) => Formatter::debug_tuple_field1_finish(f, "Float", f0),
+            Adt(f0, f1) => Formatter::debug_tuple_field2_finish(f, "Adt", f0, f1),
+            Foreign(f0) => Formatter::debug_tuple_field1_finish(f, "Foreign", f0),
+            Str => Formatter::write_str(f, "Str"),
+            Array(f0, f1) => Formatter::debug_tuple_field2_finish(f, "Array", f0, f1),
+            Slice(f0) => Formatter::debug_tuple_field1_finish(f, "Slice", f0),
+            RawPtr(f0) => Formatter::debug_tuple_field1_finish(f, "RawPtr", f0),
+            Ref(f0, f1, f2) => Formatter::debug_tuple_field3_finish(f, "Ref", f0, f1, f2),
+            FnDef(f0, f1) => Formatter::debug_tuple_field2_finish(f, "FnDef", f0, f1),
+            FnPtr(f0) => Formatter::debug_tuple_field1_finish(f, "FnPtr", f0),
+            Dynamic(f0, f1) => Formatter::debug_tuple_field2_finish(f, "Dynamic", f0, f1),
+            Closure(f0, f1) => Formatter::debug_tuple_field2_finish(f, "Closure", f0, f1),
+            Generator(f0, f1, f2) => {
+                Formatter::debug_tuple_field3_finish(f, "Generator", f0, f1, f2)
+            }
+            GeneratorWitness(f0) => Formatter::debug_tuple_field1_finish(f, "GeneratorWitness", f0),
+            Never => Formatter::write_str(f, "Never"),
+            Tuple(f0) => Formatter::debug_tuple_field1_finish(f, "Tuple", f0),
+            Projection(f0) => Formatter::debug_tuple_field1_finish(f, "Projection", f0),
+            Opaque(f0, f1) => Formatter::debug_tuple_field2_finish(f, "Opaque", f0, f1),
+            Param(f0) => Formatter::debug_tuple_field1_finish(f, "Param", f0),
+            Bound(f0, f1) => Formatter::debug_tuple_field2_finish(f, "Bound", f0, f1),
+            Placeholder(f0) => Formatter::debug_tuple_field1_finish(f, "Placeholder", f0),
+            Infer(f0) => Formatter::debug_tuple_field1_finish(f, "Infer", f0),
+            TyKind::Error(f0) => Formatter::debug_tuple_field1_finish(f, "Error", f0),
         }
     }
 }
diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs
index 9e4a574818a14..1d4be42b4a213 100644
--- a/library/core/src/fmt/mod.rs
+++ b/library/core/src/fmt/mod.rs
@@ -4,6 +4,7 @@
 
 use crate::cell::{Cell, Ref, RefCell, RefMut, SyncUnsafeCell, UnsafeCell};
 use crate::char::EscapeDebugExtArgs;
+use crate::iter;
 use crate::marker::PhantomData;
 use crate::mem;
 use crate::num::fmt as numfmt;
@@ -693,7 +694,7 @@ pub(crate) mod macros {
     /// Derive macro generating an impl of the trait `Debug`.
     #[rustc_builtin_macro]
     #[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
-    #[allow_internal_unstable(core_intrinsics)]
+    #[allow_internal_unstable(core_intrinsics, fmt_helpers_for_derive)]
     pub macro Debug($item:item) {
         /* compiler built-in */
     }
@@ -1964,6 +1965,129 @@ impl<'a> Formatter<'a> {
         builders::debug_struct_new(self, name)
     }
 
+    /// Used to shrink `derive(Debug)` code, for faster compilation and smaller binaries.
+    /// `debug_struct_fields_finish` is more general, but this is faster for 1 field.
+    #[doc(hidden)]
+    #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]
+    pub fn debug_struct_field1_finish<'b>(
+        &'b mut self,
+        name: &str,
+        name1: &str,
+        value1: &dyn Debug,
+    ) -> Result {
+        let mut builder = builders::debug_struct_new(self, name);
+        builder.field(name1, value1);
+        builder.finish()
+    }
+
+    /// Used to shrink `derive(Debug)` code, for faster compilation and smaller binaries.
+    /// `debug_struct_fields_finish` is more general, but this is faster for 2 fields.
+    #[doc(hidden)]
+    #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]
+    pub fn debug_struct_field2_finish<'b>(
+        &'b mut self,
+        name: &str,
+        name1: &str,
+        value1: &dyn Debug,
+        name2: &str,
+        value2: &dyn Debug,
+    ) -> Result {
+        let mut builder = builders::debug_struct_new(self, name);
+        builder.field(name1, value1);
+        builder.field(name2, value2);
+        builder.finish()
+    }
+
+    /// Used to shrink `derive(Debug)` code, for faster compilation and smaller binaries.
+    /// `debug_struct_fields_finish` is more general, but this is faster for 3 fields.
+    #[doc(hidden)]
+    #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]
+    pub fn debug_struct_field3_finish<'b>(
+        &'b mut self,
+        name: &str,
+        name1: &str,
+        value1: &dyn Debug,
+        name2: &str,
+        value2: &dyn Debug,
+        name3: &str,
+        value3: &dyn Debug,
+    ) -> Result {
+        let mut builder = builders::debug_struct_new(self, name);
+        builder.field(name1, value1);
+        builder.field(name2, value2);
+        builder.field(name3, value3);
+        builder.finish()
+    }
+
+    /// Used to shrink `derive(Debug)` code, for faster compilation and smaller binaries.
+    /// `debug_struct_fields_finish` is more general, but this is faster for 4 fields.
+    #[doc(hidden)]
+    #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]
+    pub fn debug_struct_field4_finish<'b>(
+        &'b mut self,
+        name: &str,
+        name1: &str,
+        value1: &dyn Debug,
+        name2: &str,
+        value2: &dyn Debug,
+        name3: &str,
+        value3: &dyn Debug,
+        name4: &str,
+        value4: &dyn Debug,
+    ) -> Result {
+        let mut builder = builders::debug_struct_new(self, name);
+        builder.field(name1, value1);
+        builder.field(name2, value2);
+        builder.field(name3, value3);
+        builder.field(name4, value4);
+        builder.finish()
+    }
+
+    /// Used to shrink `derive(Debug)` code, for faster compilation and smaller binaries.
+    /// `debug_struct_fields_finish` is more general, but this is faster for 5 fields.
+    #[doc(hidden)]
+    #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]
+    pub fn debug_struct_field5_finish<'b>(
+        &'b mut self,
+        name: &str,
+        name1: &str,
+        value1: &dyn Debug,
+        name2: &str,
+        value2: &dyn Debug,
+        name3: &str,
+        value3: &dyn Debug,
+        name4: &str,
+        value4: &dyn Debug,
+        name5: &str,
+        value5: &dyn Debug,
+    ) -> Result {
+        let mut builder = builders::debug_struct_new(self, name);
+        builder.field(name1, value1);
+        builder.field(name2, value2);
+        builder.field(name3, value3);
+        builder.field(name4, value4);
+        builder.field(name5, value5);
+        builder.finish()
+    }
+
+    /// Used to shrink `derive(Debug)` code, for faster compilation and smaller binaries.
+    /// For the cases not covered by `debug_struct_field[12345]_finish`.
+    #[doc(hidden)]
+    #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]
+    pub fn debug_struct_fields_finish<'b>(
+        &'b mut self,
+        name: &str,
+        names: &[&str],
+        values: &[&dyn Debug],
+    ) -> Result {
+        assert_eq!(names.len(), values.len());
+        let mut builder = builders::debug_struct_new(self, name);
+        for (name, value) in iter::zip(names, values) {
+            builder.field(name, value);
+        }
+        builder.finish()
+    }
+
     /// Creates a `DebugTuple` builder designed to assist with creation of
     /// `fmt::Debug` implementations for tuple structs.
     ///
@@ -1995,6 +2119,108 @@ impl<'a> Formatter<'a> {
         builders::debug_tuple_new(self, name)
     }
 
+    /// Used to shrink `derive(Debug)` code, for faster compilation and smaller binaries.
+    /// `debug_tuple_fields_finish` is more general, but this is faster for 1 field.
+    #[doc(hidden)]
+    #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]
+    pub fn debug_tuple_field1_finish<'b>(&'b mut self, name: &str, value1: &dyn Debug) -> Result {
+        let mut builder = builders::debug_tuple_new(self, name);
+        builder.field(value1);
+        builder.finish()
+    }
+
+    /// Used to shrink `derive(Debug)` code, for faster compilation and smaller binaries.
+    /// `debug_tuple_fields_finish` is more general, but this is faster for 2 fields.
+    #[doc(hidden)]
+    #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]
+    pub fn debug_tuple_field2_finish<'b>(
+        &'b mut self,
+        name: &str,
+        value1: &dyn Debug,
+        value2: &dyn Debug,
+    ) -> Result {
+        let mut builder = builders::debug_tuple_new(self, name);
+        builder.field(value1);
+        builder.field(value2);
+        builder.finish()
+    }
+
+    /// Used to shrink `derive(Debug)` code, for faster compilation and smaller binaries.
+    /// `debug_tuple_fields_finish` is more general, but this is faster for 3 fields.
+    #[doc(hidden)]
+    #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]
+    pub fn debug_tuple_field3_finish<'b>(
+        &'b mut self,
+        name: &str,
+        value1: &dyn Debug,
+        value2: &dyn Debug,
+        value3: &dyn Debug,
+    ) -> Result {
+        let mut builder = builders::debug_tuple_new(self, name);
+        builder.field(value1);
+        builder.field(value2);
+        builder.field(value3);
+        builder.finish()
+    }
+
+    /// Used to shrink `derive(Debug)` code, for faster compilation and smaller binaries.
+    /// `debug_tuple_fields_finish` is more general, but this is faster for 4 fields.
+    #[doc(hidden)]
+    #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]
+    pub fn debug_tuple_field4_finish<'b>(
+        &'b mut self,
+        name: &str,
+        value1: &dyn Debug,
+        value2: &dyn Debug,
+        value3: &dyn Debug,
+        value4: &dyn Debug,
+    ) -> Result {
+        let mut builder = builders::debug_tuple_new(self, name);
+        builder.field(value1);
+        builder.field(value2);
+        builder.field(value3);
+        builder.field(value4);
+        builder.finish()
+    }
+
+    /// Used to shrink `derive(Debug)` code, for faster compilation and smaller binaries.
+    /// `debug_tuple_fields_finish` is more general, but this is faster for 5 fields.
+    #[doc(hidden)]
+    #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]
+    pub fn debug_tuple_field5_finish<'b>(
+        &'b mut self,
+        name: &str,
+        value1: &dyn Debug,
+        value2: &dyn Debug,
+        value3: &dyn Debug,
+        value4: &dyn Debug,
+        value5: &dyn Debug,
+    ) -> Result {
+        let mut builder = builders::debug_tuple_new(self, name);
+        builder.field(value1);
+        builder.field(value2);
+        builder.field(value3);
+        builder.field(value4);
+        builder.field(value5);
+        builder.finish()
+    }
+
+    /// Used to shrink `derive(Debug)` code, for faster compilation and smaller binaries.
+    /// For the cases not covered by `debug_tuple_field[12345]_finish`.
+    #[doc(hidden)]
+    #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]
+    pub fn debug_tuple_fields_finish<'b>(
+        &'b mut self,
+        name: &str,
+        values: &[&dyn Debug],
+    ) -> Result {
+        let mut builder = builders::debug_tuple_new(self, name);
+        for value in values {
+            builder.field(value);
+        }
+        builder.finish()
+    }
+
     /// Creates a `DebugList` builder designed to assist with creation of
     /// `fmt::Debug` implementations for list-like structures.
     ///