Skip to content

Commit 7721d46

Browse files
committed
Utilize discriminant_value for more efficient deriving
The new code generated for deriving on enums looks something like this: ```rust let __self0_vi = unsafe { std::intrinsics::discriminant_value(&self) } as i32; let __self1_vi = unsafe { std::intrinsics::discriminant_value(&__arg1) } as i32; let __self2_vi = unsafe { std::intrinsics::discriminant_value(&__arg2) } as i32; /// if __self0_vi == __self1_vi && __self0_vi == __self2_vi && ... { match (...) { (Variant1, Variant1, ...) => Body1 (Variant2, Variant2, ...) => Body2, ... _ => ::core::intrinsics::unreachable() } } else { ... // catch-all remainder can inspect above variant index values. } ``` This helps massively for C-like enums since they will be compiled as a single comparison giving observed speedups of up to 8x. For more complex enums the speedup is more difficult to measure but it should not be slower to generate code this way regardless.
1 parent a9f1e29 commit 7721d46

File tree

1 file changed

+85
-39
lines changed
  • src/libsyntax/ext/deriving/generic

1 file changed

+85
-39
lines changed

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

Lines changed: 85 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,15 +1048,23 @@ impl<'a> MethodDef<'a> {
10481048
/// discriminant values. See issue #15523.)
10491049
10501050
/// ```{.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, ... };
1051+
/// let __self0_vi = unsafe {
1052+
/// std::intrinsics::discriminant_value(&self) } as i32;
1053+
/// let __self1_vi = unsafe {
1054+
/// std::intrinsics::discriminant_value(&__arg1) } as i32;
1055+
/// let __self2_vi = unsafe {
1056+
/// std::intrinsics::discriminant_value(&__arg2) } as i32;
1057+
///
1058+
/// if __self0_vi == __self1_vi && __self0_vi == __self2_vi && ... {
1059+
/// match (...) {
1060+
/// (Variant1, Variant1, ...) => Body1
1061+
/// (Variant2, Variant2, ...) => Body2,
1062+
/// ...
1063+
/// _ => ::core::intrinsics::unreachable()
1064+
/// }
1065+
/// }
1066+
/// else {
10581067
/// ... // catch-all remainder can inspect above variant index values.
1059-
/// }
10601068
/// }
10611069
/// ```
10621070
fn build_enum_match_tuple<'b>(
@@ -1187,7 +1195,6 @@ impl<'a> MethodDef<'a> {
11871195

11881196
cx.arm(sp, vec![single_pat], arm_expr)
11891197
}).collect();
1190-
11911198
// We will usually need the catch-all after matching the
11921199
// tuples `(VariantK, VariantK, ...)` for each VariantK of the
11931200
// enum. But:
@@ -1223,9 +1230,14 @@ impl<'a> MethodDef<'a> {
12231230
// ```
12241231
let mut index_let_stmts: Vec<P<ast::Stmt>> = Vec::new();
12251232

1233+
//We also build an expression which checks whether all discriminants are equal
1234+
// discriminant_test = __self0_vi == __self1_vi && __self0_vi == __self2_vi && ...
1235+
let mut discriminant_test = cx.expr_bool(sp, true);
1236+
12261237
let target_type_name =
12271238
find_repr_type_name(&cx.parse_sess.span_diagnostic, type_attrs);
12281239

1240+
let mut first_ident = None;
12291241
for (&ident, self_arg) in vi_idents.iter().zip(&self_args) {
12301242
let path = vec![cx.ident_of_std("core"),
12311243
cx.ident_of("intrinsics"),
@@ -1243,32 +1255,64 @@ impl<'a> MethodDef<'a> {
12431255
let variant_disr = cx.expr_cast(sp, variant_value, target_ty);
12441256
let let_stmt = cx.stmt_let(sp, false, ident, variant_disr);
12451257
index_let_stmts.push(let_stmt);
1258+
1259+
match first_ident {
1260+
Some(first) => {
1261+
let first_expr = cx.expr_ident(sp, first);
1262+
let id = cx.expr_ident(sp, ident);
1263+
let test = cx.expr_binary(sp, ast::BiEq, first_expr, id);
1264+
discriminant_test = cx.expr_binary(sp, ast::BiAnd, discriminant_test, test)
1265+
}
1266+
None => {
1267+
first_ident = Some(ident);
1268+
}
1269+
}
12461270
}
12471271

12481272
let arm_expr = self.call_substructure_method(
12491273
cx, trait_, type_ident, &self_args[..], nonself_args,
12501274
&catch_all_substructure);
12511275

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

1322-
return cx.expr_unreachable(sp);
1366+
cx.expr_unreachable(sp)
1367+
}
1368+
else {
1369+
1370+
// Final wrinkle: the self_args are expressions that deref
1371+
// down to desired l-values, but we cannot actually deref
1372+
// them when they are fed as r-values into a tuple
1373+
// expression; here add a layer of borrowing, turning
1374+
// `(*self, *__arg_0, ...)` into `(&*self, &*__arg_0, ...)`.
1375+
let borrowed_self_args = self_args.move_map(|self_arg| cx.expr_addr_of(sp, self_arg));
1376+
let match_arg = cx.expr(sp, ast::ExprTup(borrowed_self_args));
1377+
cx.expr_match(sp, match_arg, match_arms)
13231378
}
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)
13331379
}
13341380

13351381
fn expand_static_enum_method_body(&self,

0 commit comments

Comments
 (0)