Auto merge of #103659 - clubby789:improve-partialord-derive, r=nagisa
Special-case deriving `PartialOrd` for enums with dataless variants I was able to get slightly better codegen by flipping the derived `PartialOrd` logic for two-variant enums. I also tried to document the implementation of the derive macro to make the special-case logic a little clearer. ```rs #[derive(PartialEq, PartialOrd)] pub enum A<T> { A, B(T) } ``` ```diff impl<T: ::core::cmp::PartialOrd> ::core::cmp::PartialOrd for A<T> { #[inline] fn partial_cmp( &self, other: &A<T>, ) -> ::core::option::Option<::core::cmp::Ordering> { let __self_tag = ::core::intrinsics::discriminant_value(self); let __arg1_tag = ::core::intrinsics::discriminant_value(other); - match ::core::cmp::PartialOrd::partial_cmp(&__self_tag, &__arg1_tag) { - ::core::option::Option::Some(::core::cmp::Ordering::Equal) => { - match (self, other) { - (A::B(__self_0), A::B(__arg1_0)) => { - ::core::cmp::PartialOrd::partial_cmp(__self_0, __arg1_0) - } - _ => ::core::option::Option::Some(::core::cmp::Ordering::Equal), - } + match (self, other) { + (A::B(__self_0), A::B(__arg1_0)) => { + ::core::cmp::PartialOrd::partial_cmp(__self_0, __arg1_0) } - cmp => cmp, + _ => ::core::cmp::PartialOrd::partial_cmp(&__self_tag, &__arg1_tag), } } } ``` Godbolt: [Current](https://godbolt.org/z/GYjEzG1T8), [New](https://godbolt.org/z/GoK78qx15) I'm not sure how common a case comparing two enums like this (such as `Option`) is, and if it's worth the slowdown of adding a special case to the derive. If it causes overall regressions it might be worth just manually implementing this for `Option`.
This commit is contained in:
commit
9f82651a5f
2 changed files with 96 additions and 37 deletions
|
@ -1,7 +1,7 @@
|
||||||
use crate::deriving::generic::ty::*;
|
use crate::deriving::generic::ty::*;
|
||||||
use crate::deriving::generic::*;
|
use crate::deriving::generic::*;
|
||||||
use crate::deriving::{path_std, pathvec_std};
|
use crate::deriving::{path_std, pathvec_std};
|
||||||
use rustc_ast::MetaItem;
|
use rustc_ast::{ExprKind, ItemKind, MetaItem, PatKind};
|
||||||
use rustc_expand::base::{Annotatable, ExtCtxt};
|
use rustc_expand::base::{Annotatable, ExtCtxt};
|
||||||
use rustc_span::symbol::{sym, Ident};
|
use rustc_span::symbol::{sym, Ident};
|
||||||
use rustc_span::Span;
|
use rustc_span::Span;
|
||||||
|
@ -21,6 +21,27 @@ pub fn expand_deriving_partial_ord(
|
||||||
|
|
||||||
let attrs = thin_vec![cx.attr_word(sym::inline, span)];
|
let attrs = thin_vec![cx.attr_word(sym::inline, span)];
|
||||||
|
|
||||||
|
// Order in which to perform matching
|
||||||
|
let tag_then_data = if let Annotatable::Item(item) = item
|
||||||
|
&& let ItemKind::Enum(def, _) = &item.kind {
|
||||||
|
let dataful: Vec<bool> = def.variants.iter().map(|v| !v.data.fields().is_empty()).collect();
|
||||||
|
match dataful.iter().filter(|&&b| b).count() {
|
||||||
|
// No data, placing the tag check first makes codegen simpler
|
||||||
|
0 => true,
|
||||||
|
1..=2 => false,
|
||||||
|
_ => {
|
||||||
|
(0..dataful.len()-1).any(|i| {
|
||||||
|
if dataful[i] && let Some(idx) = dataful[i+1..].iter().position(|v| *v) {
|
||||||
|
idx >= 2
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
let partial_cmp_def = MethodDef {
|
let partial_cmp_def = MethodDef {
|
||||||
name: sym::partial_cmp,
|
name: sym::partial_cmp,
|
||||||
generics: Bounds::empty(),
|
generics: Bounds::empty(),
|
||||||
|
@ -30,7 +51,7 @@ pub fn expand_deriving_partial_ord(
|
||||||
attributes: attrs,
|
attributes: attrs,
|
||||||
fieldless_variants_strategy: FieldlessVariantsStrategy::Unify,
|
fieldless_variants_strategy: FieldlessVariantsStrategy::Unify,
|
||||||
combine_substructure: combine_substructure(Box::new(|cx, span, substr| {
|
combine_substructure: combine_substructure(Box::new(|cx, span, substr| {
|
||||||
cs_partial_cmp(cx, span, substr)
|
cs_partial_cmp(cx, span, substr, tag_then_data)
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,7 +68,12 @@ pub fn expand_deriving_partial_ord(
|
||||||
trait_def.expand(cx, mitem, item, push)
|
trait_def.expand(cx, mitem, item, push)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cs_partial_cmp(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
|
fn cs_partial_cmp(
|
||||||
|
cx: &mut ExtCtxt<'_>,
|
||||||
|
span: Span,
|
||||||
|
substr: &Substructure<'_>,
|
||||||
|
tag_then_data: bool,
|
||||||
|
) -> BlockOrExpr {
|
||||||
let test_id = Ident::new(sym::cmp, span);
|
let test_id = Ident::new(sym::cmp, span);
|
||||||
let equal_path = cx.path_global(span, cx.std_path(&[sym::cmp, sym::Ordering, sym::Equal]));
|
let equal_path = cx.path_global(span, cx.std_path(&[sym::cmp, sym::Ordering, sym::Equal]));
|
||||||
let partial_cmp_path = cx.std_path(&[sym::cmp, sym::PartialOrd, sym::partial_cmp]);
|
let partial_cmp_path = cx.std_path(&[sym::cmp, sym::PartialOrd, sym::partial_cmp]);
|
||||||
|
@ -74,13 +100,51 @@ pub fn cs_partial_cmp(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_
|
||||||
let args = vec![field.self_expr.clone(), other_expr.clone()];
|
let args = vec![field.self_expr.clone(), other_expr.clone()];
|
||||||
cx.expr_call_global(field.span, partial_cmp_path.clone(), args)
|
cx.expr_call_global(field.span, partial_cmp_path.clone(), args)
|
||||||
}
|
}
|
||||||
CsFold::Combine(span, expr1, expr2) => {
|
CsFold::Combine(span, mut expr1, expr2) => {
|
||||||
|
// When the item is an enum, this expands to
|
||||||
|
// ```
|
||||||
|
// match (expr2) {
|
||||||
|
// Some(Ordering::Equal) => expr1,
|
||||||
|
// cmp => cmp
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
// where `expr2` is `partial_cmp(self_tag, other_tag)`, and `expr1` is a `match`
|
||||||
|
// against the enum variants. This means that we begin by comparing the enum tags,
|
||||||
|
// before either inspecting their contents (if they match), or returning
|
||||||
|
// the `cmp::Ordering` of comparing the enum tags.
|
||||||
|
// ```
|
||||||
|
// match partial_cmp(self_tag, other_tag) {
|
||||||
|
// Some(Ordering::Equal) => match (self, other) {
|
||||||
|
// (Self::A(self_0), Self::A(other_0)) => partial_cmp(self_0, other_0),
|
||||||
|
// (Self::B(self_0), Self::B(other_0)) => partial_cmp(self_0, other_0),
|
||||||
|
// _ => Some(Ordering::Equal)
|
||||||
|
// }
|
||||||
|
// cmp => cmp
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
// If we have any certain enum layouts, flipping this results in better codegen
|
||||||
|
// ```
|
||||||
|
// match (self, other) {
|
||||||
|
// (Self::A(self_0), Self::A(other_0)) => partial_cmp(self_0, other_0),
|
||||||
|
// _ => partial_cmp(self_tag, other_tag)
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
// Reference: https://github.com/rust-lang/rust/pull/103659#issuecomment-1328126354
|
||||||
|
|
||||||
|
if !tag_then_data
|
||||||
|
&& let ExprKind::Match(_, arms) = &mut expr1.kind
|
||||||
|
&& let Some(last) = arms.last_mut()
|
||||||
|
&& let PatKind::Wild = last.pat.kind {
|
||||||
|
last.body = expr2;
|
||||||
|
expr1
|
||||||
|
} else {
|
||||||
let eq_arm =
|
let eq_arm =
|
||||||
cx.arm(span, cx.pat_some(span, cx.pat_path(span, equal_path.clone())), expr1);
|
cx.arm(span, cx.pat_some(span, cx.pat_path(span, equal_path.clone())), expr1);
|
||||||
let neq_arm =
|
let neq_arm =
|
||||||
cx.arm(span, cx.pat_ident(span, test_id), cx.expr_ident(span, test_id));
|
cx.arm(span, cx.pat_ident(span, test_id), cx.expr_ident(span, test_id));
|
||||||
cx.expr_match(span, expr2, vec![eq_arm, neq_arm])
|
cx.expr_match(span, expr2, vec![eq_arm, neq_arm])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
CsFold::Fieldless => cx.expr_some(span, cx.expr_path(equal_path.clone())),
|
CsFold::Fieldless => cx.expr_some(span, cx.expr_path(equal_path.clone())),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -889,23 +889,20 @@ impl ::core::cmp::PartialOrd for Mixed {
|
||||||
-> ::core::option::Option<::core::cmp::Ordering> {
|
-> ::core::option::Option<::core::cmp::Ordering> {
|
||||||
let __self_tag = ::core::intrinsics::discriminant_value(self);
|
let __self_tag = ::core::intrinsics::discriminant_value(self);
|
||||||
let __arg1_tag = ::core::intrinsics::discriminant_value(other);
|
let __arg1_tag = ::core::intrinsics::discriminant_value(other);
|
||||||
match ::core::cmp::PartialOrd::partial_cmp(&__self_tag, &__arg1_tag) {
|
|
||||||
::core::option::Option::Some(::core::cmp::Ordering::Equal) =>
|
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Mixed::R(__self_0), Mixed::R(__arg1_0)) =>
|
(Mixed::R(__self_0), Mixed::R(__arg1_0)) =>
|
||||||
::core::cmp::PartialOrd::partial_cmp(__self_0, __arg1_0),
|
::core::cmp::PartialOrd::partial_cmp(__self_0, __arg1_0),
|
||||||
(Mixed::S { d1: __self_0, d2: __self_1 }, Mixed::S {
|
(Mixed::S { d1: __self_0, d2: __self_1 }, Mixed::S {
|
||||||
d1: __arg1_0, d2: __arg1_1 }) =>
|
d1: __arg1_0, d2: __arg1_1 }) =>
|
||||||
match ::core::cmp::PartialOrd::partial_cmp(__self_0,
|
match ::core::cmp::PartialOrd::partial_cmp(__self_0, __arg1_0)
|
||||||
__arg1_0) {
|
{
|
||||||
::core::option::Option::Some(::core::cmp::Ordering::Equal)
|
::core::option::Option::Some(::core::cmp::Ordering::Equal)
|
||||||
=> ::core::cmp::PartialOrd::partial_cmp(__self_1, __arg1_1),
|
=> ::core::cmp::PartialOrd::partial_cmp(__self_1, __arg1_1),
|
||||||
cmp => cmp,
|
cmp => cmp,
|
||||||
},
|
},
|
||||||
_ =>
|
_ =>
|
||||||
::core::option::Option::Some(::core::cmp::Ordering::Equal),
|
::core::cmp::PartialOrd::partial_cmp(&__self_tag,
|
||||||
},
|
&__arg1_tag),
|
||||||
cmp => cmp,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1019,8 +1016,6 @@ impl ::core::cmp::PartialOrd for Fielded {
|
||||||
-> ::core::option::Option<::core::cmp::Ordering> {
|
-> ::core::option::Option<::core::cmp::Ordering> {
|
||||||
let __self_tag = ::core::intrinsics::discriminant_value(self);
|
let __self_tag = ::core::intrinsics::discriminant_value(self);
|
||||||
let __arg1_tag = ::core::intrinsics::discriminant_value(other);
|
let __arg1_tag = ::core::intrinsics::discriminant_value(other);
|
||||||
match ::core::cmp::PartialOrd::partial_cmp(&__self_tag, &__arg1_tag) {
|
|
||||||
::core::option::Option::Some(::core::cmp::Ordering::Equal) =>
|
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Fielded::X(__self_0), Fielded::X(__arg1_0)) =>
|
(Fielded::X(__self_0), Fielded::X(__arg1_0)) =>
|
||||||
::core::cmp::PartialOrd::partial_cmp(__self_0, __arg1_0),
|
::core::cmp::PartialOrd::partial_cmp(__self_0, __arg1_0),
|
||||||
|
@ -1028,9 +1023,9 @@ impl ::core::cmp::PartialOrd for Fielded {
|
||||||
::core::cmp::PartialOrd::partial_cmp(__self_0, __arg1_0),
|
::core::cmp::PartialOrd::partial_cmp(__self_0, __arg1_0),
|
||||||
(Fielded::Z(__self_0), Fielded::Z(__arg1_0)) =>
|
(Fielded::Z(__self_0), Fielded::Z(__arg1_0)) =>
|
||||||
::core::cmp::PartialOrd::partial_cmp(__self_0, __arg1_0),
|
::core::cmp::PartialOrd::partial_cmp(__self_0, __arg1_0),
|
||||||
_ => unsafe { ::core::intrinsics::unreachable() }
|
_ =>
|
||||||
},
|
::core::cmp::PartialOrd::partial_cmp(&__self_tag,
|
||||||
cmp => cmp,
|
&__arg1_tag),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue