Skip to content

Commit 014a5c1

Browse files
committed
Auto merge of #26280 - Marwes:deriving_discriminant, r=pcwalton
PR for #26128. Improves codegen in deriving by utilizing the discriminant_value intrinsic. I have a more detailed comment on the changes in a comment on the issue [here](#26128 (comment)) ### Old ``` running 7 tests test large_c_like ... bench: 2,694,129 ns/iter (+/- 5,170) test large_c_like_ord ... bench: 2,723,521 ns/iter (+/- 9,098) test test1_partial_eq ... bench: 2,439,317 ns/iter (+/- 2,135) test test1_partial_ord ... bench: 2,499,114 ns/iter (+/- 55,766) test test2_partial_eq ... bench: 3,562,815 ns/iter (+/- 45,590) test test2_partial_ord ... bench: 3,398,306 ns/iter (+/- 22,180) test test_match_success ... bench: 1,509,267 ns/iter (+/- 1,982) ``` ### New ``` running 7 tests test large_c_like ... bench: 286,509 ns/iter (+/- 474) test large_c_like_ord ... bench: 286,666 ns/iter (+/- 8,756) test test1_partial_eq ... bench: 286,584 ns/iter (+/- 2,789) test test1_partial_ord ... bench: 286,470 ns/iter (+/- 516) test test2_partial_eq ... bench: 2,228,997 ns/iter (+/- 34,191) test test2_partial_ord ... bench: 1,731,699 ns/iter (+/- 21,756) test test_match_success ... bench: 1,509,630 ns/iter (+[- 3,765) ``` [Benchmark](https://gist.github.com/Marwes/7c0b3468d0cae972a2b4)
2 parents 467e4a6 + 34d5b54 commit 014a5c1

File tree

1 file changed

+90
-42
lines changed
  • src/libsyntax/ext/deriving/generic

1 file changed

+90
-42
lines changed

src/libsyntax/ext/deriving/generic/mod.rs

Lines changed: 90 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,21 +1042,31 @@ impl<'a> MethodDef<'a> {
10421042
/// variants where all of the variants match, and one catch-all for
10431043
/// when one does not match.
10441044
1045+
/// As an optimization we generate code which checks whether all variants
1046+
/// match first which makes llvm see that C-like enums can be compiled into
1047+
/// a simple equality check (for PartialEq).
1048+
10451049
/// The catch-all handler is provided access the variant index values
1046-
/// for each of the self-args, carried in precomputed variables. (Nota
1047-
/// bene: the variant index values are not necessarily the
1048-
/// discriminant values. See issue #15523.)
1050+
/// for each of the self-args, carried in precomputed variables.
10491051
10501052
/// ```{.text}
1051-
/// match (this, that, ...) {
1052-
/// (Variant1, Variant1, Variant1) => ... // delegate Matching on Variant1
1053-
/// (Variant2, Variant2, Variant2) => ... // delegate Matching on Variant2
1054-
/// ...
1055-
/// _ => {
1056-
/// let __this_vi = match this { Variant1 => 0, Variant2 => 1, ... };
1057-
/// let __that_vi = match that { Variant1 => 0, Variant2 => 1, ... };
1053+
/// let __self0_vi = unsafe {
1054+
/// std::intrinsics::discriminant_value(&self) } as i32;
1055+
/// let __self1_vi = unsafe {
1056+
/// std::intrinsics::discriminant_value(&__arg1) } as i32;
1057+
/// let __self2_vi = unsafe {
1058+
/// std::intrinsics::discriminant_value(&__arg2) } as i32;
1059+
///
1060+
/// if __self0_vi == __self1_vi && __self0_vi == __self2_vi && ... {
1061+
/// match (...) {
1062+
/// (Variant1, Variant1, ...) => Body1
1063+
/// (Variant2, Variant2, ...) => Body2,
1064+
/// ...
1065+
/// _ => ::core::intrinsics::unreachable()
1066+
/// }
1067+
/// }
1068+
/// else {
10581069
/// ... // catch-all remainder can inspect above variant index values.
1059-
/// }
10601070
/// }
10611071
/// ```
10621072
fn build_enum_match_tuple<'b>(
@@ -1187,7 +1197,6 @@ impl<'a> MethodDef<'a> {
11871197

11881198
cx.arm(sp, vec![single_pat], arm_expr)
11891199
}).collect();
1190-
11911200
// We will usually need the catch-all after matching the
11921201
// tuples `(VariantK, VariantK, ...)` for each VariantK of the
11931202
// enum. But:
@@ -1223,9 +1232,14 @@ impl<'a> MethodDef<'a> {
12231232
// ```
12241233
let mut index_let_stmts: Vec<P<ast::Stmt>> = Vec::new();
12251234

1235+
//We also build an expression which checks whether all discriminants are equal
1236+
// discriminant_test = __self0_vi == __self1_vi && __self0_vi == __self2_vi && ...
1237+
let mut discriminant_test = cx.expr_bool(sp, true);
1238+
12261239
let target_type_name =
12271240
find_repr_type_name(&cx.parse_sess.span_diagnostic, type_attrs);
12281241

1242+
let mut first_ident = None;
12291243
for (&ident, self_arg) in vi_idents.iter().zip(&self_args) {
12301244
let path = vec![cx.ident_of_std("core"),
12311245
cx.ident_of("intrinsics"),
@@ -1243,32 +1257,64 @@ impl<'a> MethodDef<'a> {
12431257
let variant_disr = cx.expr_cast(sp, variant_value, target_ty);
12441258
let let_stmt = cx.stmt_let(sp, false, ident, variant_disr);
12451259
index_let_stmts.push(let_stmt);
1260+
1261+
match first_ident {
1262+
Some(first) => {
1263+
let first_expr = cx.expr_ident(sp, first);
1264+
let id = cx.expr_ident(sp, ident);
1265+
let test = cx.expr_binary(sp, ast::BiEq, first_expr, id);
1266+
discriminant_test = cx.expr_binary(sp, ast::BiAnd, discriminant_test, test)
1267+
}
1268+
None => {
1269+
first_ident = Some(ident);
1270+
}
1271+
}
12461272
}
12471273

12481274
let arm_expr = self.call_substructure_method(
12491275
cx, trait_, type_ident, &self_args[..], nonself_args,
12501276
&catch_all_substructure);
12511277

1252-
// Builds the expression:
1253-
// {
1254-
// let __self0_vi = ...;
1255-
// let __self1_vi = ...;
1256-
// ...
1257-
// <delegated expression referring to __self0_vi, et al.>
1258-
// }
1259-
let arm_expr = cx.expr_block(
1260-
cx.block_all(sp, index_let_stmts, Some(arm_expr)));
1261-
1262-
// Builds arm:
1263-
// _ => { let __self0_vi = ...;
1264-
// let __self1_vi = ...;
1265-
// ...
1266-
// <delegated expression as above> }
1267-
let catch_all_match_arm =
1268-
cx.arm(sp, vec![cx.pat_wild(sp)], arm_expr);
1269-
1270-
match_arms.push(catch_all_match_arm);
1271-
1278+
//Since we know that all the arguments will match if we reach the match expression we
1279+
//add the unreachable intrinsics as the result of the catch all which should help llvm
1280+
//in optimizing it
1281+
let path = vec![cx.ident_of_std("core"),
1282+
cx.ident_of("intrinsics"),
1283+
cx.ident_of("unreachable")];
1284+
let call = cx.expr_call_global(
1285+
sp, path, vec![]);
1286+
let unreachable = cx.expr_block(P(ast::Block {
1287+
stmts: vec![],
1288+
expr: Some(call),
1289+
id: ast::DUMMY_NODE_ID,
1290+
rules: ast::UnsafeBlock(ast::CompilerGenerated),
1291+
span: sp }));
1292+
match_arms.push(cx.arm(sp, vec![cx.pat_wild(sp)], unreachable));
1293+
1294+
// Final wrinkle: the self_args are expressions that deref
1295+
// down to desired l-values, but we cannot actually deref
1296+
// them when they are fed as r-values into a tuple
1297+
// expression; here add a layer of borrowing, turning
1298+
// `(*self, *__arg_0, ...)` into `(&*self, &*__arg_0, ...)`.
1299+
let borrowed_self_args = self_args.move_map(|self_arg| cx.expr_addr_of(sp, self_arg));
1300+
let match_arg = cx.expr(sp, ast::ExprTup(borrowed_self_args));
1301+
1302+
//Lastly we create an expression which branches on all discriminants being equal
1303+
// if discriminant_test {
1304+
// match (...) {
1305+
// (Variant1, Variant1, ...) => Body1
1306+
// (Variant2, Variant2, ...) => Body2,
1307+
// ...
1308+
// _ => ::core::intrinsics::unreachable()
1309+
// }
1310+
// }
1311+
// else {
1312+
// <delegated expression referring to __self0_vi, et al.>
1313+
// }
1314+
let all_match = cx.expr_match(sp, match_arg, match_arms);
1315+
let arm_expr = cx.expr_if(sp, discriminant_test, all_match, Some(arm_expr));
1316+
cx.expr_block(
1317+
cx.block_all(sp, index_let_stmts, Some(arm_expr)))
12721318
} else if variants.is_empty() {
12731319
// As an additional wrinkle, For a zero-variant enum A,
12741320
// currently the compiler
@@ -1319,17 +1365,19 @@ impl<'a> MethodDef<'a> {
13191365
// derive Debug on such a type could here generate code
13201366
// that needs the feature gate enabled.)
13211367

1322-
return cx.expr_unreachable(sp);
1368+
cx.expr_unreachable(sp)
1369+
}
1370+
else {
1371+
1372+
// Final wrinkle: the self_args are expressions that deref
1373+
// down to desired l-values, but we cannot actually deref
1374+
// them when they are fed as r-values into a tuple
1375+
// expression; here add a layer of borrowing, turning
1376+
// `(*self, *__arg_0, ...)` into `(&*self, &*__arg_0, ...)`.
1377+
let borrowed_self_args = self_args.move_map(|self_arg| cx.expr_addr_of(sp, self_arg));
1378+
let match_arg = cx.expr(sp, ast::ExprTup(borrowed_self_args));
1379+
cx.expr_match(sp, match_arg, match_arms)
13231380
}
1324-
1325-
// Final wrinkle: the self_args are expressions that deref
1326-
// down to desired l-values, but we cannot actually deref
1327-
// them when they are fed as r-values into a tuple
1328-
// expression; here add a layer of borrowing, turning
1329-
// `(*self, *__arg_0, ...)` into `(&*self, &*__arg_0, ...)`.
1330-
let borrowed_self_args = self_args.move_map(|self_arg| cx.expr_addr_of(sp, self_arg));
1331-
let match_arg = cx.expr(sp, ast::ExprTup(borrowed_self_args));
1332-
cx.expr_match(sp, match_arg, match_arms)
13331381
}
13341382

13351383
fn expand_static_enum_method_body(&self,

0 commit comments

Comments
 (0)