Add chaining versions of lt/le/gt/ge and use them in tuple PartialOrd

This commit is contained in:
Scott McMurray 2025-03-06 15:55:40 -08:00
parent b54ca0e433
commit 35248c6830
5 changed files with 174 additions and 329 deletions

View file

@ -29,6 +29,7 @@ mod bytewise;
pub(crate) use bytewise::BytewiseEq;
use self::Ordering::*;
use crate::ops::ControlFlow::{self, Break, Continue};
/// Trait for comparisons using the equality operator.
///
@ -1446,6 +1447,54 @@ pub macro PartialOrd($item:item) {
/* compiler built-in */
}
/// Helpers for chaining together field PartialOrds into the full type's ordering.
///
/// If the two values are equal, returns `ControlFlow::Continue`.
/// If the two values are not equal, returns `ControlFlow::Break(self OP other)`.
///
/// This allows simple types like `i32` and `f64` to just emit two comparisons
/// directly, instead of needing to optimize the 3-way comparison.
///
/// Currently this is done using specialization, but it doesn't need that:
/// it could be provided methods on `PartialOrd` instead and work fine.
pub(crate) trait SpecChainingPartialOrd<Rhs>: PartialOrd<Rhs> {
fn spec_chain_lt(&self, other: &Rhs) -> ControlFlow<bool>;
fn spec_chain_le(&self, other: &Rhs) -> ControlFlow<bool>;
fn spec_chain_gt(&self, other: &Rhs) -> ControlFlow<bool>;
fn spec_chain_ge(&self, other: &Rhs) -> ControlFlow<bool>;
}
impl<T: PartialOrd<U>, U> SpecChainingPartialOrd<U> for T {
#[inline]
default fn spec_chain_lt(&self, other: &U) -> ControlFlow<bool> {
match PartialOrd::partial_cmp(self, other) {
Some(Equal) => Continue(()),
c => Break(c.is_some_and(Ordering::is_lt)),
}
}
#[inline]
default fn spec_chain_le(&self, other: &U) -> ControlFlow<bool> {
match PartialOrd::partial_cmp(self, other) {
Some(Equal) => Continue(()),
c => Break(c.is_some_and(Ordering::is_le)),
}
}
#[inline]
default fn spec_chain_gt(&self, other: &U) -> ControlFlow<bool> {
match PartialOrd::partial_cmp(self, other) {
Some(Equal) => Continue(()),
c => Break(c.is_some_and(Ordering::is_gt)),
}
}
#[inline]
default fn spec_chain_ge(&self, other: &U) -> ControlFlow<bool> {
match PartialOrd::partial_cmp(self, other) {
Some(Equal) => Continue(()),
c => Break(c.is_some_and(Ordering::is_ge)),
}
}
}
/// Compares and returns the minimum of two values.
///
/// Returns the first argument if the comparison determines them to be equal.
@ -1741,6 +1790,7 @@ where
mod impls {
use crate::cmp::Ordering::{self, Equal, Greater, Less};
use crate::hint::unreachable_unchecked;
use crate::ops::ControlFlow::{self, Break, Continue};
macro_rules! partial_eq_impl {
($($t:ty)*) => ($(
@ -1779,6 +1829,36 @@ mod impls {
eq_impl! { () bool char usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 }
macro_rules! chaining_impl {
($t:ty) => {
// These implementations are the same for `Ord` or `PartialOrd` types
// because if either is NAN the `==` test will fail so we end up in
// the `Break` case and the comparison will correctly return `false`.
impl super::SpecChainingPartialOrd<$t> for $t {
#[inline]
fn spec_chain_lt(&self, other: &Self) -> ControlFlow<bool> {
let (lhs, rhs) = (*self, *other);
if lhs == rhs { Continue(()) } else { Break(lhs < rhs) }
}
#[inline]
fn spec_chain_le(&self, other: &Self) -> ControlFlow<bool> {
let (lhs, rhs) = (*self, *other);
if lhs == rhs { Continue(()) } else { Break(lhs <= rhs) }
}
#[inline]
fn spec_chain_gt(&self, other: &Self) -> ControlFlow<bool> {
let (lhs, rhs) = (*self, *other);
if lhs == rhs { Continue(()) } else { Break(lhs > rhs) }
}
#[inline]
fn spec_chain_ge(&self, other: &Self) -> ControlFlow<bool> {
let (lhs, rhs) = (*self, *other);
if lhs == rhs { Continue(()) } else { Break(lhs >= rhs) }
}
}
};
}
macro_rules! partial_ord_impl {
($($t:ty)*) => ($(
#[stable(feature = "rust1", since = "1.0.0")]
@ -1801,6 +1881,8 @@ mod impls {
#[inline(always)]
fn gt(&self, other: &$t) -> bool { (*self) > (*other) }
}
chaining_impl!($t);
)*)
}
@ -1840,6 +1922,8 @@ mod impls {
fn gt(&self, other: &$t) -> bool { (*self) > (*other) }
}
chaining_impl!($t);
#[stable(feature = "rust1", since = "1.0.0")]
impl Ord for $t {
#[inline]

View file

@ -1,7 +1,9 @@
// See core/src/primitive_docs.rs for documentation.
use crate::cmp::Ordering::{self, *};
use crate::cmp::SpecChainingPartialOrd;
use crate::marker::{ConstParamTy_, StructuralPartialEq, UnsizedConstParamTy};
use crate::ops::ControlFlow::{Break, Continue};
// Recursive macro for implementing n-ary tuple functions and operations
//
@ -80,19 +82,19 @@ macro_rules! tuple_impls {
}
#[inline]
fn lt(&self, other: &($($T,)+)) -> bool {
lexical_ord!(lt, Less, $( ${ignore($T)} self.${index()}, other.${index()} ),+)
lexical_ord!(lt, spec_chain_lt, $( ${ignore($T)} self.${index()}, other.${index()} ),+)
}
#[inline]
fn le(&self, other: &($($T,)+)) -> bool {
lexical_ord!(le, Less, $( ${ignore($T)} self.${index()}, other.${index()} ),+)
lexical_ord!(le, spec_chain_le, $( ${ignore($T)} self.${index()}, other.${index()} ),+)
}
#[inline]
fn ge(&self, other: &($($T,)+)) -> bool {
lexical_ord!(ge, Greater, $( ${ignore($T)} self.${index()}, other.${index()} ),+)
lexical_ord!(ge, spec_chain_ge, $( ${ignore($T)} self.${index()}, other.${index()} ),+)
}
#[inline]
fn gt(&self, other: &($($T,)+)) -> bool {
lexical_ord!(gt, Greater, $( ${ignore($T)} self.${index()}, other.${index()} ),+)
lexical_ord!(gt, spec_chain_gt, $( ${ignore($T)} self.${index()}, other.${index()} ),+)
}
}
}
@ -171,15 +173,16 @@ macro_rules! maybe_tuple_doc {
// `(a1, a2, a3) < (b1, b2, b3)` would be `lexical_ord!(lt, opt_is_lt, a1, b1,
// a2, b2, a3, b3)` (and similarly for `lexical_cmp`)
//
// `$ne_rel` is only used to determine the result after checking that they're
// not equal, so `lt` and `le` can both just use `Less`.
// `$chain_rel` is the method from `SpecChainingPartialOrd` to use for all but the
// final value, to produce better results for simple primitives.
macro_rules! lexical_ord {
($rel: ident, $ne_rel: ident, $a:expr, $b:expr, $($rest_a:expr, $rest_b:expr),+) => {{
let c = PartialOrd::partial_cmp(&$a, &$b);
if c != Some(Equal) { c == Some($ne_rel) }
else { lexical_ord!($rel, $ne_rel, $($rest_a, $rest_b),+) }
($rel: ident, $chain_rel: ident, $a:expr, $b:expr, $($rest_a:expr, $rest_b:expr),+) => {{
match SpecChainingPartialOrd::$chain_rel(&$a, &$b) {
Break(val) => val,
Continue(()) => lexical_ord!($rel, $chain_rel, $($rest_a, $rest_b),+),
}
}};
($rel: ident, $ne_rel: ident, $a:expr, $b:expr) => {
($rel: ident, $chain_rel: ident, $a:expr, $b:expr) => {
// Use the specific method for the last element
PartialOrd::$rel(&$a, &$b)
};

View file

@ -4,234 +4,67 @@ fn demo_ge_partial(_1: &(f32, f32), _2: &(f32, f32)) -> bool {
debug a => _1;
debug b => _2;
let mut _0: bool;
scope 1 (inlined std::cmp::impls::<impl PartialOrd for &(f32, f32)>::le) {
scope 2 (inlined core::tuple::<impl PartialOrd for (f32, f32)>::le) {
let mut _12: bool;
let _15: std::option::Option<std::cmp::Ordering>;
let _19: &f32;
let _20: &f32;
scope 1 (inlined std::cmp::impls::<impl PartialOrd for &(f32, f32)>::ge) {
scope 2 (inlined core::tuple::<impl PartialOrd for (f32, f32)>::ge) {
let mut _7: std::ops::ControlFlow<bool>;
let _8: bool;
scope 3 {
let mut _9: &std::option::Option<std::cmp::Ordering>;
let mut _13: &std::option::Option<std::cmp::Ordering>;
scope 4 (inlined <Option<std::cmp::Ordering> as PartialEq>::ne) {
let mut _11: bool;
scope 5 (inlined <Option<std::cmp::Ordering> as PartialEq>::eq) {
let mut _10: isize;
let mut _16: isize;
scope 6 {
scope 7 (inlined <std::cmp::Ordering as PartialEq>::eq) {
let _17: i8;
scope 8 {
let _18: i8;
scope 9 {
}
}
}
}
}
}
scope 10 (inlined <Option<std::cmp::Ordering> as PartialEq>::eq) {
let mut _14: isize;
let mut _21: isize;
scope 11 {
scope 12 (inlined <std::cmp::Ordering as PartialEq>::eq) {
let _22: i8;
scope 13 {
let _23: i8;
scope 14 {
}
}
}
}
}
}
scope 15 (inlined std::cmp::impls::<impl PartialOrd for f32>::partial_cmp) {
scope 4 (inlined std::cmp::impls::<impl std::cmp::SpecChainingPartialOrd<f32> for f32>::spec_chain_ge) {
let mut _3: f32;
let mut _4: f32;
let mut _5: bool;
let mut _6: f32;
let mut _7: f32;
let mut _8: bool;
let mut _6: bool;
scope 5 {
}
}
scope 6 (inlined std::cmp::impls::<impl PartialOrd for f32>::ge) {
let mut _9: f32;
let mut _10: f32;
}
}
}
bb0: {
StorageLive(_19);
StorageLive(_20);
StorageLive(_13);
StorageLive(_9);
StorageLive(_15);
StorageLive(_5);
StorageLive(_8);
StorageLive(_3);
_3 = copy ((*_1).0: f32);
StorageLive(_4);
_4 = copy ((*_2).0: f32);
_5 = Le(move _3, move _4);
StorageDead(_4);
StorageDead(_3);
StorageLive(_6);
_6 = copy ((*_1).0: f32);
StorageLive(_7);
_7 = copy ((*_2).0: f32);
_8 = Ge(move _6, move _7);
StorageDead(_7);
StorageDead(_6);
switchInt(copy _5) -> [0: bb1, otherwise: bb5];
StorageLive(_3);
StorageLive(_4);
_3 = copy ((*_1).0: f32);
_4 = copy ((*_2).0: f32);
StorageLive(_5);
_5 = Eq(copy _3, copy _4);
switchInt(move _5) -> [0: bb1, otherwise: bb2];
}
bb1: {
switchInt(copy _8) -> [0: bb2, otherwise: bb4];
StorageLive(_6);
_6 = Ge(copy _3, copy _4);
_7 = ControlFlow::<bool>::Break(move _6);
StorageDead(_6);
StorageDead(_5);
StorageDead(_4);
StorageDead(_3);
_8 = copy ((_7 as Break).0: bool);
_0 = copy _8;
goto -> bb3;
}
bb2: {
StorageDead(_8);
StorageDead(_5);
StorageLive(_12);
_9 = const core::tuple::<impl std::cmp::PartialOrd for (f32, f32)>::le::promoted[1];
StorageLive(_11);
StorageLive(_16);
StorageDead(_4);
StorageDead(_3);
StorageLive(_9);
_9 = copy ((*_1).1: f32);
StorageLive(_10);
_10 = discriminant((*_9));
_11 = Eq(copy _10, const 0_isize);
_10 = copy ((*_2).1: f32);
_0 = Ge(move _9, move _10);
StorageDead(_10);
StorageDead(_16);
_12 = Not(move _11);
StorageDead(_11);
switchInt(move _12) -> [0: bb11, otherwise: bb3];
StorageDead(_9);
goto -> bb3;
}
bb3: {
_13 = const core::tuple::<impl std::cmp::PartialOrd for (f32, f32)>::le::promoted[0];
StorageLive(_21);
StorageLive(_14);
_14 = discriminant((*_13));
_0 = Eq(copy _14, const 0_isize);
goto -> bb16;
}
bb4: {
_15 = const Option::<std::cmp::Ordering>::Some(Greater);
StorageDead(_8);
StorageDead(_5);
StorageLive(_12);
_9 = const core::tuple::<impl std::cmp::PartialOrd for (f32, f32)>::le::promoted[1];
StorageLive(_11);
StorageLive(_16);
StorageLive(_10);
goto -> bb8;
}
bb5: {
switchInt(copy _8) -> [0: bb6, otherwise: bb7];
}
bb6: {
_15 = const Option::<std::cmp::Ordering>::Some(Less);
StorageDead(_8);
StorageDead(_5);
StorageLive(_12);
_9 = const core::tuple::<impl std::cmp::PartialOrd for (f32, f32)>::le::promoted[1];
StorageLive(_11);
StorageLive(_16);
StorageLive(_10);
goto -> bb8;
}
bb7: {
_15 = const Option::<std::cmp::Ordering>::Some(Equal);
StorageDead(_8);
StorageDead(_5);
StorageLive(_12);
_9 = const core::tuple::<impl std::cmp::PartialOrd for (f32, f32)>::le::promoted[1];
StorageLive(_11);
StorageLive(_16);
StorageLive(_10);
goto -> bb8;
}
bb8: {
_16 = discriminant((*_9));
switchInt(move _16) -> [0: bb9, 1: bb10, otherwise: bb18];
}
bb9: {
StorageDead(_10);
StorageDead(_16);
StorageDead(_11);
_13 = const core::tuple::<impl std::cmp::PartialOrd for (f32, f32)>::le::promoted[0];
StorageLive(_21);
StorageLive(_14);
goto -> bb13;
}
bb10: {
StorageLive(_17);
StorageLive(_18);
_17 = discriminant(((_15 as Some).0: std::cmp::Ordering));
_18 = discriminant((((*_9) as Some).0: std::cmp::Ordering));
_11 = Eq(copy _17, copy _18);
StorageDead(_18);
StorageDead(_17);
StorageDead(_10);
StorageDead(_16);
_12 = Not(move _11);
StorageDead(_11);
switchInt(move _12) -> [0: bb11, otherwise: bb12];
}
bb11: {
_19 = &((*_1).1: f32);
_20 = &((*_2).1: f32);
_0 = <f32 as PartialOrd>::le(move _19, move _20) -> [return: bb17, unwind continue];
}
bb12: {
_13 = const core::tuple::<impl std::cmp::PartialOrd for (f32, f32)>::le::promoted[0];
StorageLive(_21);
StorageLive(_14);
goto -> bb13;
}
bb13: {
_21 = discriminant((*_13));
switchInt(move _21) -> [0: bb14, 1: bb15, otherwise: bb18];
}
bb14: {
_0 = const false;
goto -> bb16;
}
bb15: {
StorageLive(_22);
StorageLive(_23);
_22 = discriminant(((_15 as Some).0: std::cmp::Ordering));
_23 = discriminant((((*_13) as Some).0: std::cmp::Ordering));
_0 = Eq(copy _22, copy _23);
StorageDead(_23);
StorageDead(_22);
goto -> bb16;
}
bb16: {
StorageDead(_14);
StorageDead(_21);
goto -> bb17;
}
bb17: {
StorageDead(_12);
StorageDead(_15);
StorageDead(_9);
StorageDead(_13);
StorageDead(_20);
StorageDead(_19);
StorageDead(_7);
return;
}
bb18: {
unreachable;
}
}

View file

@ -6,140 +6,65 @@ fn demo_le_total(_1: &(u16, i16), _2: &(u16, i16)) -> bool {
let mut _0: bool;
scope 1 (inlined std::cmp::impls::<impl PartialOrd for &(u16, i16)>::le) {
scope 2 (inlined core::tuple::<impl PartialOrd for (u16, i16)>::le) {
let mut _12: bool;
let _13: &i16;
let _14: &i16;
let mut _7: std::ops::ControlFlow<bool>;
let _8: bool;
scope 3 {
let mut _6: &std::option::Option<std::cmp::Ordering>;
let mut _8: &std::option::Option<std::cmp::Ordering>;
scope 4 (inlined <Option<std::cmp::Ordering> as PartialEq>::ne) {
let mut _11: bool;
scope 5 (inlined <Option<std::cmp::Ordering> as PartialEq>::eq) {
let mut _7: isize;
scope 6 {
scope 7 (inlined <std::cmp::Ordering as PartialEq>::eq) {
let _9: i8;
scope 8 {
let _10: i8;
scope 9 {
}
}
}
}
}
}
scope 10 (inlined <Option<std::cmp::Ordering> as PartialEq>::eq) {
let mut _15: isize;
scope 11 {
scope 12 (inlined <std::cmp::Ordering as PartialEq>::eq) {
let _16: i8;
scope 13 {
let _17: i8;
scope 14 {
}
}
}
}
}
}
scope 15 (inlined std::cmp::impls::<impl PartialOrd for u16>::partial_cmp) {
scope 4 (inlined std::cmp::impls::<impl std::cmp::SpecChainingPartialOrd<u16> for u16>::spec_chain_le) {
let mut _3: u16;
let mut _4: u16;
let mut _5: std::cmp::Ordering;
let mut _5: bool;
let mut _6: bool;
scope 5 {
}
}
scope 6 (inlined std::cmp::impls::<impl PartialOrd for i16>::le) {
let mut _9: i16;
let mut _10: i16;
}
}
}
bb0: {
StorageLive(_13);
StorageLive(_14);
StorageLive(_8);
StorageLive(_6);
StorageLive(_3);
_3 = copy ((*_1).0: u16);
StorageLive(_4);
_4 = copy ((*_2).0: u16);
_5 = Cmp(move _3, move _4);
StorageDead(_4);
StorageDead(_3);
StorageLive(_12);
_6 = const core::tuple::<impl std::cmp::PartialOrd for (u16, i16)>::le::promoted[1];
StorageLive(_11);
StorageLive(_7);
_7 = discriminant((*_6));
switchInt(move _7) -> [0: bb1, 1: bb2, otherwise: bb10];
StorageLive(_3);
StorageLive(_4);
_3 = copy ((*_1).0: u16);
_4 = copy ((*_2).0: u16);
StorageLive(_5);
_5 = Eq(copy _3, copy _4);
switchInt(move _5) -> [0: bb1, otherwise: bb2];
}
bb1: {
StorageDead(_7);
StorageDead(_11);
_8 = const core::tuple::<impl std::cmp::PartialOrd for (u16, i16)>::le::promoted[0];
StorageLive(_15);
goto -> bb5;
StorageLive(_6);
_6 = Le(copy _3, copy _4);
_7 = ControlFlow::<bool>::Break(move _6);
StorageDead(_6);
StorageDead(_5);
StorageDead(_4);
StorageDead(_3);
_8 = copy ((_7 as Break).0: bool);
_0 = copy _8;
goto -> bb3;
}
bb2: {
StorageDead(_5);
StorageDead(_4);
StorageDead(_3);
StorageLive(_9);
_9 = copy ((*_1).1: i16);
StorageLive(_10);
_9 = discriminant(_5);
_10 = discriminant((((*_6) as Some).0: std::cmp::Ordering));
_11 = Eq(copy _9, copy _10);
_10 = copy ((*_2).1: i16);
_0 = Le(move _9, move _10);
StorageDead(_10);
StorageDead(_9);
StorageDead(_7);
_12 = Not(move _11);
StorageDead(_11);
switchInt(move _12) -> [0: bb3, otherwise: bb4];
goto -> bb3;
}
bb3: {
_13 = &((*_1).1: i16);
_14 = &((*_2).1: i16);
_0 = <i16 as PartialOrd>::le(move _13, move _14) -> [return: bb9, unwind continue];
}
bb4: {
_8 = const core::tuple::<impl std::cmp::PartialOrd for (u16, i16)>::le::promoted[0];
StorageLive(_15);
goto -> bb5;
}
bb5: {
_15 = discriminant((*_8));
switchInt(move _15) -> [0: bb6, 1: bb7, otherwise: bb10];
}
bb6: {
_0 = const false;
goto -> bb8;
}
bb7: {
StorageLive(_16);
StorageLive(_17);
_16 = discriminant(_5);
_17 = discriminant((((*_8) as Some).0: std::cmp::Ordering));
_0 = Eq(copy _16, copy _17);
StorageDead(_17);
StorageDead(_16);
goto -> bb8;
}
bb8: {
StorageDead(_15);
goto -> bb9;
}
bb9: {
StorageDead(_12);
StorageDead(_6);
StorageDead(_8);
StorageDead(_14);
StorageDead(_13);
StorageDead(_7);
return;
}
bb10: {
unreachable;
}
}

View file

@ -1,4 +1,4 @@
//@ compile-flags: -O -Zmir-opt-level=2 -Cdebuginfo=0 -Z inline-mir-hint-threshold=9999
//@ compile-flags: -O -Zmir-opt-level=2 -Cdebuginfo=0
//@ needs-unwind
#![crate_type = "lib"]
@ -12,5 +12,5 @@ pub fn demo_le_total(a: &(u16, i16), b: &(u16, i16)) -> bool {
// EMIT_MIR tuple_ord.demo_ge_partial.PreCodegen.after.mir
pub fn demo_ge_partial(a: &(f32, f32), b: &(f32, f32)) -> bool {
// CHECK-LABEL: demo_ge_partial
a <= b
a >= b
}