Skip to content

Commit

Permalink
Optimize the code produced by derive(Debug).
Browse files Browse the repository at this point in the history
This commit adds new methods that combine sequences of existing
formatting methods.
- `Formatter::debug_{tuple,struct}_field[12345]_finish`, equivalent to a
  `Formatter::debug_{tuple,struct}` + N x `Debug{Tuple,Struct}::field` +
  `Debug{Tuple,Struct}::finish` call sequence.
- `Formatter::debug_{tuple,struct}_fields_finish` is similar, but can
  handle any number of fields by using arrays.

These new methods are all marked as `doc(hidden)` and unstable. They are
intended for the compiler's own use.

Special-casing up to 5 fields gives significantly better performance
results than always using arrays (as was tried in rust-lang#95637).

The commit also changes the `Debug` deriving code to use these new methods. For
example, where the old `Debug` code for a struct with two fields would be like
this:
```
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
    match *self {
	Self {
	    f1: ref __self_0_0,
	    f2: ref __self_0_1,
	} => {
	    let debug_trait_builder = &mut ::core::fmt::Formatter::debug_struct(f, "S2");
	    let _ = ::core::fmt::DebugStruct::field(debug_trait_builder, "f1", &&(*__self_0_0));
	    let _ = ::core::fmt::DebugStruct::field(debug_trait_builder, "f2", &&(*__self_0_1));
	    ::core::fmt::DebugStruct::finish(debug_trait_builder)
	}
    }
}
```
the new code is like this:
```
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
    match *self {
	Self {
	    f1: ref __self_0_0,
	    f2: ref __self_0_1,
	} => ::core::fmt::Formatter::debug_struct_field2_finish(
	    f,
	    "S2",
	    "f1",
	    &&(*__self_0_0),
	    "f2",
	    &&(*__self_0_1),
	),
    }
}
```
This shrinks the code produced for `Debug` instances
considerably, reducing compile times and binary sizes.

Co-authored-by: Scott McMurray <scottmcm@users.noreply.github.com>
  • Loading branch information
nnethercote and scottmcm committed Jun 23, 2022
1 parent 7586e79 commit 5b54363
Show file tree
Hide file tree
Showing 4 changed files with 366 additions and 90 deletions.
205 changes: 117 additions & 88 deletions compiler/rustc_builtin_macros/src/deriving/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<'_>,
Expand Down Expand Up @@ -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),
Expand All @@ -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))
}
}
21 changes: 20 additions & 1 deletion compiler/rustc_expand/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down Expand Up @@ -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)
Expand All @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 5b54363

Please sign in to comment.