Skip to content

Commit 088d417

Browse files
committed
Auto merge of #33593 - dotdash:smart_derive, r=brson
Improve derived implementations for enums with lots of fieldless variants A number of trait methods like PartialEq::eq or Hash::hash don't actually need a distinct arm for each variant, because the code within the arm only depends on the number and types of the fields in the variants. We can easily exploit this fact to create less and better code for enums with multiple variants that have no fields at all, the extreme case being C-like enums. For nickel.rs and its by now infamous 800 variant enum, this reduces optimized compile times by 25% and non-optimized compile times by 40%. Also peak memory usage is down by almost 40% (310MB down to 190MB). To be fair, most other crates don't benefit nearly as much, because they don't have as huge enums. The crates in the Rust distribution that I measured saw basically no change in compile times (I only tried optimized builds) and only 1-2% reduction in peak memory usage.
2 parents dd0ef17 + 0eeb14e commit 088d417

File tree

13 files changed

+44
-7
lines changed

13 files changed

+44
-7
lines changed

src/libsyntax_ext/deriving/clone.rs

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub fn expand_deriving_clone(cx: &mut ExtCtxt,
3939
// Clone + Copy, and then there'd be no Clone impl at all if the user fills in something
4040
// that is Clone but not Copy. and until specialization we can't write both impls.
4141
let bounds;
42+
let unify_fieldless_variants;
4243
let substructure;
4344
match *item {
4445
Annotatable::Item(ref annitem) => {
@@ -49,13 +50,15 @@ pub fn expand_deriving_clone(cx: &mut ExtCtxt,
4950
&& attr::contains_name(&annitem.attrs, "derive_Copy") => {
5051

5152
bounds = vec![Literal(path_std!(cx, core::marker::Copy))];
53+
unify_fieldless_variants = true;
5254
substructure = combine_substructure(Box::new(|c, s, sub| {
5355
cs_clone("Clone", c, s, sub, Mode::Shallow)
5456
}));
5557
}
5658

5759
_ => {
5860
bounds = vec![];
61+
unify_fieldless_variants = false;
5962
substructure = combine_substructure(Box::new(|c, s, sub| {
6063
cs_clone("Clone", c, s, sub, Mode::Deep)
6164
}));
@@ -84,6 +87,7 @@ pub fn expand_deriving_clone(cx: &mut ExtCtxt,
8487
ret_ty: Self_,
8588
attributes: attrs,
8689
is_unsafe: false,
90+
unify_fieldless_variants: unify_fieldless_variants,
8791
combine_substructure: substructure,
8892
}
8993
),

src/libsyntax_ext/deriving/cmp/eq.rs

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub fn expand_deriving_eq(cx: &mut ExtCtxt,
6262
ret_ty: nil_ty(),
6363
attributes: attrs,
6464
is_unsafe: false,
65+
unify_fieldless_variants: true,
6566
combine_substructure: combine_substructure(Box::new(|a, b, c| {
6667
cs_total_eq_assert(a, b, c)
6768
}))

src/libsyntax_ext/deriving/cmp/ord.rs

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub fn expand_deriving_ord(cx: &mut ExtCtxt,
4242
ret_ty: Literal(path_std!(cx, core::cmp::Ordering)),
4343
attributes: attrs,
4444
is_unsafe: false,
45+
unify_fieldless_variants: true,
4546
combine_substructure: combine_substructure(Box::new(|a, b, c| {
4647
cs_cmp(a, b, c)
4748
})),

src/libsyntax_ext/deriving/cmp/partial_eq.rs

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ pub fn expand_deriving_partial_eq(cx: &mut ExtCtxt,
7373
ret_ty: Literal(path_local!(bool)),
7474
attributes: attrs,
7575
is_unsafe: false,
76+
unify_fieldless_variants: true,
7677
combine_substructure: combine_substructure(Box::new(|a, b, c| {
7778
$f(a, b, c)
7879
}))

src/libsyntax_ext/deriving/cmp/partial_ord.rs

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub fn expand_deriving_partial_ord(cx: &mut ExtCtxt,
3838
ret_ty: Literal(path_local!(bool)),
3939
attributes: attrs,
4040
is_unsafe: false,
41+
unify_fieldless_variants: true,
4142
combine_substructure: combine_substructure(Box::new(|cx, span, substr| {
4243
cs_op($op, $equal, cx, span, substr)
4344
}))
@@ -62,6 +63,7 @@ pub fn expand_deriving_partial_ord(cx: &mut ExtCtxt,
6263
ret_ty: ret_ty,
6364
attributes: attrs,
6465
is_unsafe: false,
66+
unify_fieldless_variants: true,
6567
combine_substructure: combine_substructure(Box::new(|cx, span, substr| {
6668
cs_partial_cmp(cx, span, substr)
6769
}))

src/libsyntax_ext/deriving/debug.rs

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub fn expand_deriving_debug(cx: &mut ExtCtxt,
4545
ret_ty: Literal(path_std!(cx, core::fmt::Result)),
4646
attributes: Vec::new(),
4747
is_unsafe: false,
48+
unify_fieldless_variants: false,
4849
combine_substructure: combine_substructure(Box::new(|a, b, c| {
4950
show_substructure(a, b, c)
5051
}))

src/libsyntax_ext/deriving/decodable.rs

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ fn expand_deriving_decodable_imp(cx: &mut ExtCtxt,
8585
)),
8686
attributes: Vec::new(),
8787
is_unsafe: false,
88+
unify_fieldless_variants: false,
8889
combine_substructure: combine_substructure(Box::new(|a, b, c| {
8990
decodable_substructure(a, b, c, krate)
9091
})),

src/libsyntax_ext/deriving/default.rs

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub fn expand_deriving_default(cx: &mut ExtCtxt,
4242
ret_ty: Self_,
4343
attributes: attrs,
4444
is_unsafe: false,
45+
unify_fieldless_variants: false,
4546
combine_substructure: combine_substructure(Box::new(|a, b, c| {
4647
default_substructure(a, b, c)
4748
}))

src/libsyntax_ext/deriving/encodable.rs

+1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ fn expand_deriving_encodable_imp(cx: &mut ExtCtxt,
161161
)),
162162
attributes: Vec::new(),
163163
is_unsafe: false,
164+
unify_fieldless_variants: false,
164165
combine_substructure: combine_substructure(Box::new(|a, b, c| {
165166
encodable_substructure(a, b, c, krate)
166167
})),

src/libsyntax_ext/deriving/generic/mod.rs

+28-7
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@ pub struct MethodDef<'a> {
257257
// Is it an `unsafe fn`?
258258
pub is_unsafe: bool,
259259

260+
/// Can we combine fieldless variants for enums into a single match arm?
261+
pub unify_fieldless_variants: bool,
262+
260263
pub combine_substructure: RefCell<CombineSubstructureFunc<'a>>,
261264
}
262265

@@ -1131,12 +1134,15 @@ impl<'a> MethodDef<'a> {
11311134
let catch_all_substructure = EnumNonMatchingCollapsed(
11321135
self_arg_idents, &variants[..], &vi_idents[..]);
11331136

1137+
let first_fieldless = variants.iter().find(|v| v.node.data.fields().is_empty());
1138+
11341139
// These arms are of the form:
11351140
// (Variant1, Variant1, ...) => Body1
11361141
// (Variant2, Variant2, ...) => Body2
11371142
// ...
11381143
// where each tuple has length = self_args.len()
11391144
let mut match_arms: Vec<ast::Arm> = variants.iter().enumerate()
1145+
.filter(|&(_, v)| !(self.unify_fieldless_variants && v.node.data.fields().is_empty()))
11401146
.map(|(index, variant)| {
11411147
let mk_self_pat = |cx: &mut ExtCtxt, self_arg_name: &str| {
11421148
let (p, idents) = trait_.create_enum_variant_pattern(
@@ -1219,6 +1225,28 @@ impl<'a> MethodDef<'a> {
12191225

12201226
cx.arm(sp, vec![single_pat], arm_expr)
12211227
}).collect();
1228+
1229+
let default = match first_fieldless {
1230+
Some(v) if self.unify_fieldless_variants => {
1231+
// We need a default case that handles the fieldless variants.
1232+
// The index and actual variant aren't meaningful in this case,
1233+
// so just use whatever
1234+
Some(self.call_substructure_method(
1235+
cx, trait_, type_ident, &self_args[..], nonself_args,
1236+
&EnumMatching(0, v, Vec::new())))
1237+
}
1238+
_ if variants.len() > 1 && self_args.len() > 1 => {
1239+
// Since we know that all the arguments will match if we reach
1240+
// the match expression we add the unreachable intrinsics as the
1241+
// result of the catch all which should help llvm in optimizing it
1242+
Some(deriving::call_intrinsic(cx, sp, "unreachable", vec![]))
1243+
}
1244+
_ => None
1245+
};
1246+
if let Some(arm) = default {
1247+
match_arms.push(cx.arm(sp, vec![cx.pat_wild(sp)], arm));
1248+
}
1249+
12221250
// We will usually need the catch-all after matching the
12231251
// tuples `(VariantK, VariantK, ...)` for each VariantK of the
12241252
// enum. But:
@@ -1292,13 +1320,6 @@ impl<'a> MethodDef<'a> {
12921320
cx, trait_, type_ident, &self_args[..], nonself_args,
12931321
&catch_all_substructure);
12941322

1295-
//Since we know that all the arguments will match if we reach the match expression we
1296-
//add the unreachable intrinsics as the result of the catch all which should help llvm
1297-
//in optimizing it
1298-
match_arms.push(cx.arm(sp,
1299-
vec![cx.pat_wild(sp)],
1300-
deriving::call_intrinsic(cx, sp, "unreachable", vec![])));
1301-
13021323
// Final wrinkle: the self_args are expressions that deref
13031324
// down to desired l-values, but we cannot actually deref
13041325
// them when they are fed as r-values into a tuple

src/libsyntax_ext/deriving/hash.rs

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pub fn expand_deriving_hash(cx: &mut ExtCtxt,
5151
ret_ty: nil_ty(),
5252
attributes: vec![],
5353
is_unsafe: false,
54+
unify_fieldless_variants: true,
5455
combine_substructure: combine_substructure(Box::new(|a, b, c| {
5556
hash_substructure(a, b, c)
5657
}))

src/test/run-pass-fulldeps/auxiliary/custom_derive_plugin.rs

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ fn expand(cx: &mut ExtCtxt,
5858
ret_ty: Literal(Path::new_local("isize")),
5959
attributes: vec![],
6060
is_unsafe: false,
61+
unify_fieldless_variants: true,
6162
combine_substructure: combine_substructure(box |cx, span, substr| {
6263
let zero = cx.expr_isize(span, 0);
6364
cs_fold(false,

src/test/run-pass-fulldeps/auxiliary/custom_derive_plugin_attr.rs

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ fn expand(cx: &mut ExtCtxt,
6060
ret_ty: Literal(Path::new_local("isize")),
6161
attributes: vec![],
6262
is_unsafe: false,
63+
unify_fieldless_variants: true,
6364
combine_substructure: combine_substructure(Box::new(totalsum_substructure)),
6465
},
6566
],

0 commit comments

Comments
 (0)