From 889fca737dacf2fbe13cad5bad9e4fe72c15a806 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 14 Aug 2026 15:26:11 +1000 Subject: [PATCH 1/2] Simplify `core::cmp::{smallest, largest}` Implementation Remove support for 1 argument variant and adjust implementation to reduce MIR generated. --- library/core/src/cmp.rs | 93 +++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/library/core/src/cmp.rs b/library/core/src/cmp.rs index 43128302dfef4..666c60b094ffd 100644 --- a/library/core/src/cmp.rs +++ b/library/core/src/cmp.rs @@ -1935,24 +1935,19 @@ macro impl_tuples($($mac:ident,)+) { } /// Implementation detail for [`smallest`] and [`largest`]. -/// Marker indicating that `Self` is a tuple where all members are of the same type. +/// Marker indicating that `Self` is a tuple where all members are of the type `T`. +/// Cannot be an associated type as we require the empty tuple to implement this trait +/// for all types `T`. #[diagnostic::on_unimplemented(message = "`{Self}` is not a homogeneous tuple")] #[unstable(feature = "cmp_splat_internals", issue = "160728")] #[rustc_const_unstable(feature = "cmp_splat_internals", issue = "160728")] -const trait HomogeneousTuple: crate::marker::Tuple { - /// The type of each item in this tuple. - type Item; -} +const trait HomogeneousTuple: crate::marker::Tuple {} /// Implements [`HomogeneousTuple`] for a provided tuple. -macro impl_homogeneous_tuple($($($x:ident,)+)?) { - $( - #[unstable(feature = "cmp_splat_internals", issue = "160728")] - #[rustc_const_unstable(feature = "cmp_splat_internals", issue = "160728")] - const impl HomogeneousTuple for ($(${ignore($x)}T,)+) { - type Item = T; - } - )? +macro impl_homogeneous_tuple($($x:ident,)*) { + #[unstable(feature = "cmp_splat_internals", issue = "160728")] + #[rustc_const_unstable(feature = "cmp_splat_internals", issue = "160728")] + const impl HomogeneousTuple for ($(${ignore($x)}T,)*) { } } impl_tuples! { @@ -1971,7 +1966,6 @@ impl_tuples! { /// #![feature(cmp_splat)] /// use std::cmp; /// -/// assert_eq!(cmp::smallest(1), 1); /// assert_eq!(cmp::smallest(1, 2), 1); /// assert_eq!(cmp::smallest(3, 2, 1), 1); /// assert_eq!(cmp::smallest(1, 2, 3, 4), 1); @@ -2008,36 +2002,35 @@ impl_tuples! { #[expect(private_bounds, reason = "`SmallestArgs` is an internal implementation detail")] #[cfg(not(test))] // FIXME: splat interacts poorly with the double linking of `core` in tests pub const fn smallest( - #[rustc_splat] args: impl [const] SmallestArgs, + v1: T, + v2: T, + #[rustc_splat] args: impl [const] SmallestArgs, ) -> T { - SmallestArgs::smallest(args) + SmallestArgs::smallest(v1, v2, args) } /// Implementation detail for [`smallest`]. #[diagnostic::on_unimplemented(message = "`{Self}` is not a valid set of arguments for `smallest`")] #[unstable(feature = "cmp_splat_internals", issue = "160728")] #[rustc_const_unstable(feature = "cmp_splat_internals", issue = "160728")] -const trait SmallestArgs: HomogeneousTuple { +const trait SmallestArgs: HomogeneousTuple { /// Reduces all elements of a homogeneous tuple to its smallest value. - fn smallest(self) -> Self::Item; + fn smallest(v1: T, v2: T, args: Self) -> T; } /// Implements [`SmallestArgs`] for a provided tuple if applicable. -macro impl_smallest_args($($x:ident, $($($y:ident,)+)?)?) { - $( - #[unstable(feature = "cmp_splat_internals", issue = "160728")] - #[rustc_const_unstable(feature = "cmp_splat_internals", issue = "160728")] - const impl SmallestArgs for (T, $($(${ignore($y)}T,)+)?) - $(where T: [const] Destruct + [const] Ord, $(${ignore($y)})+)? - { - #[inline] - fn smallest(self) -> Self::Item { - let ($x, $($($y,)+)?) = self; - $($(let $x = $x.min($y);)+)? - $x - } +macro impl_smallest_args($($x:ident,)*) { + #[unstable(feature = "cmp_splat_internals", issue = "160728")] + #[rustc_const_unstable(feature = "cmp_splat_internals", issue = "160728")] + const impl SmallestArgs for ($(${ignore($x)}T,)*) + where + T: [const] Destruct + [const] Ord, + { + #[inline(always)] // improves unoptimised codegen + fn smallest(v1: T, v2: T, ($($x,)*): Self) -> T { + v1.min(v2)$(.min($x))* } - )? + } } impl_tuples! { @@ -2056,7 +2049,6 @@ impl_tuples! { /// #![feature(cmp_splat)] /// use std::cmp; /// -/// assert_eq!(cmp::largest(1), 1); /// assert_eq!(cmp::largest(1, 2), 2); /// assert_eq!(cmp::largest(3, 2, 1), 3); /// assert_eq!(cmp::largest(1, 2, 3, 4), 4); @@ -2093,36 +2085,35 @@ impl_tuples! { #[expect(private_bounds, reason = "`LargestArgs` is an internal implementation detail")] #[cfg(not(test))] // FIXME: splat interacts poorly with the double linking of `core` in tests pub const fn largest( - #[rustc_splat] args: impl [const] LargestArgs, + v1: T, + v2: T, + #[rustc_splat] args: impl [const] LargestArgs, ) -> T { - LargestArgs::largest(args) + LargestArgs::largest(v1, v2, args) } /// Implementation detail for [`largest`]. #[diagnostic::on_unimplemented(message = "`{Self}` is not a valid set of arguments for `largest`")] #[unstable(feature = "cmp_splat_internals", issue = "160728")] #[rustc_const_unstable(feature = "cmp_splat_internals", issue = "160728")] -const trait LargestArgs: HomogeneousTuple { +const trait LargestArgs: HomogeneousTuple { /// Reduces all elements of a homogeneous tuple to its largest value. - fn largest(self) -> Self::Item; + fn largest(v1: T, v2: T, args: Self) -> T; } /// Implements [`LargestArgs`] for a provided tuple if applicable. -macro impl_largest_args($($x:ident, $($($y:ident,)+)?)?) { - $( - #[unstable(feature = "cmp_splat_internals", issue = "160728")] - #[rustc_const_unstable(feature = "cmp_splat_internals", issue = "160728")] - const impl LargestArgs for (T, $($(${ignore($y)}T,)+)?) - $(where T: [const] Destruct + [const] Ord, $(${ignore($y)})+)? - { - #[inline] - fn largest(self) -> Self::Item { - let ($x, $($($y,)+)?) = self; - $($(let $x = $x.max($y);)+)? - $x - } +macro impl_largest_args($($x:ident,)*) { + #[unstable(feature = "cmp_splat_internals", issue = "160728")] + #[rustc_const_unstable(feature = "cmp_splat_internals", issue = "160728")] + const impl LargestArgs for ($(${ignore($x)}T,)*) + where + T: [const] Destruct + [const] Ord, + { + #[inline(always)] // improves unoptimised codegen + fn largest(v1: T, v2: T, ($($x,)*): Self) -> T { + v1.max(v2)$(.max($x))* } - )? + } } impl_tuples! { From 556f726c9357f16020c356536f215b2acefe2531 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 14 Aug 2026 15:27:39 +1000 Subject: [PATCH 2/2] Add `mir-opt` test documenting unoptimized MIR generated 2-argument variant in release mode reduces down to the same function regardless, but the debug build is affected. --- .../cmp_splat.min_direct.built.after.mir | 51 ++++++++++++++ .../cmp_splat.min_direct_u8.built.after.mir | 27 ++++++++ .../cmp_splat.min_splatted.built.after.mir | 68 +++++++++++++++++++ .../cmp_splat.min_splatted_u8.built.after.mir | 31 +++++++++ tests/mir-opt/cmp_splat.rs | 44 ++++++++++++ ...impl#0}-min_splatted_inner.built.after.mir | 56 +++++++++++++++ 6 files changed, 277 insertions(+) create mode 100644 tests/mir-opt/cmp_splat.min_direct.built.after.mir create mode 100644 tests/mir-opt/cmp_splat.min_direct_u8.built.after.mir create mode 100644 tests/mir-opt/cmp_splat.min_splatted.built.after.mir create mode 100644 tests/mir-opt/cmp_splat.min_splatted_u8.built.after.mir create mode 100644 tests/mir-opt/cmp_splat.rs create mode 100644 tests/mir-opt/cmp_splat.{impl#0}-min_splatted_inner.built.after.mir diff --git a/tests/mir-opt/cmp_splat.min_direct.built.after.mir b/tests/mir-opt/cmp_splat.min_direct.built.after.mir new file mode 100644 index 0000000000000..6d2fbd441561f --- /dev/null +++ b/tests/mir-opt/cmp_splat.min_direct.built.after.mir @@ -0,0 +1,51 @@ +// MIR for `min_direct` after built + +fn min_direct(_1: T, _2: T) -> T { + debug v1 => _1; + debug v2 => _2; + let mut _0: T; + let mut _3: T; + let mut _4: T; + + bb0: { + StorageLive(_3); + _3 = move _1; + StorageLive(_4); + _4 = move _2; + _0 = ::min(move _3, move _4) -> [return: bb1, unwind: bb4]; + } + + bb1: { + StorageDead(_4); + StorageDead(_3); + drop(_2) -> [return: bb2, unwind: bb7]; + } + + bb2: { + drop(_1) -> [return: bb3, unwind: bb8]; + } + + bb3: { + return; + } + + bb4 (cleanup): { + drop(_4) -> [return: bb5, unwind terminate(cleanup)]; + } + + bb5 (cleanup): { + drop(_3) -> [return: bb6, unwind terminate(cleanup)]; + } + + bb6 (cleanup): { + drop(_2) -> [return: bb7, unwind terminate(cleanup)]; + } + + bb7 (cleanup): { + drop(_1) -> [return: bb8, unwind terminate(cleanup)]; + } + + bb8 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/cmp_splat.min_direct_u8.built.after.mir b/tests/mir-opt/cmp_splat.min_direct_u8.built.after.mir new file mode 100644 index 0000000000000..117c6c42046c9 --- /dev/null +++ b/tests/mir-opt/cmp_splat.min_direct_u8.built.after.mir @@ -0,0 +1,27 @@ +// MIR for `min_direct_u8` after built + +fn min_direct_u8(_1: u8, _2: u8) -> u8 { + debug x => _1; + debug y => _2; + let mut _0: u8; + let mut _3: u8; + let mut _4: u8; + + bb0: { + StorageLive(_3); + _3 = copy _1; + StorageLive(_4); + _4 = copy _2; + _0 = min_direct::(move _3, move _4) -> [return: bb1, unwind: bb2]; + } + + bb1: { + StorageDead(_4); + StorageDead(_3); + return; + } + + bb2 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/cmp_splat.min_splatted.built.after.mir b/tests/mir-opt/cmp_splat.min_splatted.built.after.mir new file mode 100644 index 0000000000000..daf67730f6cbb --- /dev/null +++ b/tests/mir-opt/cmp_splat.min_splatted.built.after.mir @@ -0,0 +1,68 @@ +// MIR for `min_splatted` after built + +fn min_splatted(_1: T, _2: T, _3: impl MinArgs) -> T { + debug v1 => _1; + debug v2 => _2; + debug args => _3; + let mut _0: T; + let mut _4: T; + let mut _5: T; + let mut _6: impl MinArgs; + + bb0: { + StorageLive(_4); + _4 = move _1; + StorageLive(_5); + _5 = move _2; + StorageLive(_6); + _6 = move _3; + _0 = as MinArgs>::min_splatted_inner(move _4, move _5, move _6) -> [return: bb1, unwind: bb5]; + } + + bb1: { + StorageDead(_6); + StorageDead(_5); + StorageDead(_4); + drop(_3) -> [return: bb2, unwind: bb9]; + } + + bb2: { + drop(_2) -> [return: bb3, unwind: bb10]; + } + + bb3: { + drop(_1) -> [return: bb4, unwind: bb11]; + } + + bb4: { + return; + } + + bb5 (cleanup): { + drop(_6) -> [return: bb6, unwind terminate(cleanup)]; + } + + bb6 (cleanup): { + drop(_5) -> [return: bb7, unwind terminate(cleanup)]; + } + + bb7 (cleanup): { + drop(_4) -> [return: bb8, unwind terminate(cleanup)]; + } + + bb8 (cleanup): { + drop(_3) -> [return: bb9, unwind terminate(cleanup)]; + } + + bb9 (cleanup): { + drop(_2) -> [return: bb10, unwind terminate(cleanup)]; + } + + bb10 (cleanup): { + drop(_1) -> [return: bb11, unwind terminate(cleanup)]; + } + + bb11 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/cmp_splat.min_splatted_u8.built.after.mir b/tests/mir-opt/cmp_splat.min_splatted_u8.built.after.mir new file mode 100644 index 0000000000000..f64de3fde70c5 --- /dev/null +++ b/tests/mir-opt/cmp_splat.min_splatted_u8.built.after.mir @@ -0,0 +1,31 @@ +// MIR for `min_splatted_u8` after built + +fn min_splatted_u8(_1: u8, _2: u8) -> u8 { + debug x => _1; + debug y => _2; + let mut _0: u8; + let mut _3: u8; + let mut _4: u8; + let mut _5: (); + + bb0: { + StorageLive(_3); + _3 = copy _1; + StorageLive(_4); + _4 = copy _2; + StorageLive(_5); + _5 = (); + _0 = min_splatted::(move _3, move _4, move _5) -> [return: bb1, unwind: bb2]; + } + + bb1: { + StorageDead(_5); + StorageDead(_4); + StorageDead(_3); + return; + } + + bb2 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/cmp_splat.rs b/tests/mir-opt/cmp_splat.rs new file mode 100644 index 0000000000000..832a8472edda6 --- /dev/null +++ b/tests/mir-opt/cmp_splat.rs @@ -0,0 +1,44 @@ +//@ skip-filecheck + +#![feature(splat)] +#![feature(tuple_trait)] + +// Comparing the MIR of a splat-compatible function with it's direct counterpart. +// It's expected that there will be additional MIR generated due to the layer of +// indirection added by a trait (MinArgs). This test tracks that overhead. + +use std::marker::Tuple; + +// EMIT_MIR cmp_splat.min_direct_u8.built.after.mir +pub fn min_direct_u8(x: u8, y: u8) -> u8 { + min_direct(x, y) +} + +// EMIT_MIR cmp_splat.min_splatted_u8.built.after.mir +pub fn min_splatted_u8(x: u8, y: u8) -> u8 { + min_splatted(x, y) +} + +// EMIT_MIR cmp_splat.min_direct.built.after.mir +#[inline] +pub fn min_direct(v1: T, v2: T) -> T { + v1.min(v2) +} + +// EMIT_MIR cmp_splat.min_splatted.built.after.mir +#[inline] +pub fn min_splatted(v1: T, v2: T, #[rustc_splat] args: impl MinArgs) -> T { + MinArgs::min_splatted_inner(v1, v2, args) +} + +trait MinArgs: Tuple { + fn min_splatted_inner(v1: T, v2: T, args: Self) -> T; +} + +// EMIT_MIR cmp_splat.{impl#0}-min_splatted_inner.built.after.mir +impl MinArgs for () { + #[inline(always)] + fn min_splatted_inner(v1: T, v2: T, (): Self) -> T { + v1.min(v2) + } +} diff --git a/tests/mir-opt/cmp_splat.{impl#0}-min_splatted_inner.built.after.mir b/tests/mir-opt/cmp_splat.{impl#0}-min_splatted_inner.built.after.mir new file mode 100644 index 0000000000000..a4915f76a5ecb --- /dev/null +++ b/tests/mir-opt/cmp_splat.{impl#0}-min_splatted_inner.built.after.mir @@ -0,0 +1,56 @@ +// MIR for `::min_splatted_inner` after built + +fn ::min_splatted_inner(_1: T, _2: T, _3: ()) -> T { + debug v1 => _1; + debug v2 => _2; + let mut _0: T; + let mut _4: T; + let mut _5: T; + + bb0: { + StorageLive(_4); + _4 = move _1; + StorageLive(_5); + _5 = move _2; + _0 = ::min(move _4, move _5) -> [return: bb2, unwind: bb5]; + } + + bb1: { + FakeRead(ForMatchedPlace(None), _3); + unreachable; + } + + bb2: { + StorageDead(_5); + StorageDead(_4); + drop(_2) -> [return: bb3, unwind: bb8]; + } + + bb3: { + drop(_1) -> [return: bb4, unwind: bb9]; + } + + bb4: { + return; + } + + bb5 (cleanup): { + drop(_5) -> [return: bb6, unwind terminate(cleanup)]; + } + + bb6 (cleanup): { + drop(_4) -> [return: bb7, unwind terminate(cleanup)]; + } + + bb7 (cleanup): { + drop(_2) -> [return: bb8, unwind terminate(cleanup)]; + } + + bb8 (cleanup): { + drop(_1) -> [return: bb9, unwind terminate(cleanup)]; + } + + bb9 (cleanup): { + resume; + } +}